过程语言和功能语言之间的区别?
Difference between procedural and functional languages?
我最近在面试中被问到这个问题。我无法得到正确的答案。
当您在过程语言(如 C)和函数语言(如 haskell)中使用相同的参数重复调用函数时,在哪种语言中您可能会得到不同的结果?我在 [this] (What is the difference between procedural programming and functional programming?) post 上读到纯函数式语言总是会得到相同的答案。为什么函数式语言如此而不是过程式语言?
纯函数式语言的运行使得相同的输入总是产生相同的输出,纯函数没有副作用。然而,在过程式或非纯函数式语言中,例如 C,可能会出现副作用,例如指针被修改,或其他外部因素,例如时间或文件 I/O。甚至 Haskell 也可以做文件 I/O。因此,如果 Haskell 和 I/O 是纯函数式语言,那么 C and the cpp
are purely functional, too.
以这个C程序为例:
#ifndef _BSD_SOURCE
#define _BSD_SOURCE
#endif
#include <stdio.h>
#include <time.h>
#include <unistd.h>
int get_num(int n)
{
usleep(1100000);
return (time(NULL) - n) % (n / 10);
}
int main(void)
{
int i;
for (i = 0; i < 10; i++)
printf("%d\n", get_num(4242));
return 0;
}
我们采用输入的常量参数,4242
到 get_num
。经过任意数学运算,由于时间因素和休眠,相同的输入在这种过程语言中不会产生相同的输出。
运行 一次,我们得到:
247
248
249
250
251
253
254
255
256
257
和运行之后,我们得到:
270
271
272
273
274
275
277
278
279
280
在大多数 C 中,副作用比比皆是。但是在纯函数式语言中,这种副作用不会存在或不可能。
在命令式编程中,函数是允许有副作用的,比如修改变量的值,写入文件,访问网络等。第二次同样的函数是运行,它可以检测到以前的副作用和return不同的东西。一个简单的 C 示例是:
int i = 0;
int foo() {
return i++;
}
多次调用会生成不同的数字。再举个例子:
int foo(int *p) {
return (*p)++;
}
即使你用相同的参数调用上面的方法,即相同的指针,结果也会因为增量的不同而不同。
在纯函数式编程中,设计禁止像i++
这样的副作用。这样,函数的输出必须仅取决于其参数的值。例如。如果在 Haskell 中我们有一个类型为 Int -> Int
的函数 f
,我们可以确定 f 3
在每次调用时总是 return 相同的整数。
好吧,以上所述并不完全正确 -- 程序最终必须做一些事情 I/O,否则将毫无意义。因此,必须以某种形式提供副作用。在 Haskell 中,实际上允许有副作用的函数,但它们的类型必须表明这些不是纯函数。例如。如果函数 g
的类型为 Int -> IO Int
,那么它可以执行 I/O 并有副作用: 运行ning print =<< g 3
每次都可以打印不同的值,就像在命令式编程中。
因此,总而言之,在纯函数式编程中,具有副作用的函数与不具有副作用的函数具有不同的类型。通常,在设计良好的函数式程序中,很少需要 I/O 和副作用,纯函数构成了绝大多数代码。正因为如此,有时我们说"pure functional programming forbids side effects",因为大部分代码都是在这个约束下写的。
进一步澄清,它并不是一种 属性 语言。没有命令式语言会强制您只编写具有副作用的过程。也完全可以用纯函数式风格来编写,尽管它没有被明确支持。
而在纯函数式语言中,根本没有允许您修改变量、访问全局可见存储等的语言结构。因此,这样说有点夸张纯 FP 语言 "forbid" 不纯函数 - 相反,一开始就没有办法编写一个。请注意,即使函数如下:
printTwice x = do {putStrLn x; putStrLn x}
不不纯。 printTwice
的应用会导致 IO 操作。你可以多次这样做,将结果放在列表或元组中并传递它们,这都是纯粹的。具体来说没有什么区别:
twoActions = (printTwice "hello", printTwice "hello")
和
twoActions = (a,a) where a = printTwice "hello"
我最近在面试中被问到这个问题。我无法得到正确的答案。
当您在过程语言(如 C)和函数语言(如 haskell)中使用相同的参数重复调用函数时,在哪种语言中您可能会得到不同的结果?我在 [this] (What is the difference between procedural programming and functional programming?) post 上读到纯函数式语言总是会得到相同的答案。为什么函数式语言如此而不是过程式语言?
纯函数式语言的运行使得相同的输入总是产生相同的输出,纯函数没有副作用。然而,在过程式或非纯函数式语言中,例如 C,可能会出现副作用,例如指针被修改,或其他外部因素,例如时间或文件 I/O。甚至 Haskell 也可以做文件 I/O。因此,如果 Haskell 和 I/O 是纯函数式语言,那么 C and the cpp
are purely functional, too.
以这个C程序为例:
#ifndef _BSD_SOURCE
#define _BSD_SOURCE
#endif
#include <stdio.h>
#include <time.h>
#include <unistd.h>
int get_num(int n)
{
usleep(1100000);
return (time(NULL) - n) % (n / 10);
}
int main(void)
{
int i;
for (i = 0; i < 10; i++)
printf("%d\n", get_num(4242));
return 0;
}
我们采用输入的常量参数,4242
到 get_num
。经过任意数学运算,由于时间因素和休眠,相同的输入在这种过程语言中不会产生相同的输出。
运行 一次,我们得到:
247
248
249
250
251
253
254
255
256
257
和运行之后,我们得到:
270
271
272
273
274
275
277
278
279
280
在大多数 C 中,副作用比比皆是。但是在纯函数式语言中,这种副作用不会存在或不可能。
在命令式编程中,函数是允许有副作用的,比如修改变量的值,写入文件,访问网络等。第二次同样的函数是运行,它可以检测到以前的副作用和return不同的东西。一个简单的 C 示例是:
int i = 0;
int foo() {
return i++;
}
多次调用会生成不同的数字。再举个例子:
int foo(int *p) {
return (*p)++;
}
即使你用相同的参数调用上面的方法,即相同的指针,结果也会因为增量的不同而不同。
在纯函数式编程中,设计禁止像i++
这样的副作用。这样,函数的输出必须仅取决于其参数的值。例如。如果在 Haskell 中我们有一个类型为 Int -> Int
的函数 f
,我们可以确定 f 3
在每次调用时总是 return 相同的整数。
好吧,以上所述并不完全正确 -- 程序最终必须做一些事情 I/O,否则将毫无意义。因此,必须以某种形式提供副作用。在 Haskell 中,实际上允许有副作用的函数,但它们的类型必须表明这些不是纯函数。例如。如果函数 g
的类型为 Int -> IO Int
,那么它可以执行 I/O 并有副作用: 运行ning print =<< g 3
每次都可以打印不同的值,就像在命令式编程中。
因此,总而言之,在纯函数式编程中,具有副作用的函数与不具有副作用的函数具有不同的类型。通常,在设计良好的函数式程序中,很少需要 I/O 和副作用,纯函数构成了绝大多数代码。正因为如此,有时我们说"pure functional programming forbids side effects",因为大部分代码都是在这个约束下写的。
进一步澄清,它并不是一种 属性 语言。没有命令式语言会强制您只编写具有副作用的过程。也完全可以用纯函数式风格来编写,尽管它没有被明确支持。
而在纯函数式语言中,根本没有允许您修改变量、访问全局可见存储等的语言结构。因此,这样说有点夸张纯 FP 语言 "forbid" 不纯函数 - 相反,一开始就没有办法编写一个。请注意,即使函数如下:
printTwice x = do {putStrLn x; putStrLn x}
不不纯。 printTwice
的应用会导致 IO 操作。你可以多次这样做,将结果放在列表或元组中并传递它们,这都是纯粹的。具体来说没有什么区别:
twoActions = (printTwice "hello", printTwice "hello")
和
twoActions = (a,a) where a = printTwice "hello"