我最近在一次采访中被问到这个问题。我无法得到正确的答案。
当您重复调用过程 language(like C)中的函数和函数 language(like haskell)时,使用相同的参数,您可能会得到不同的语言?我在1帖子中读到,纯函数式语言总会产生相同的答案。为什么这样的函数式语言而不是过程语言呢?
在命令式编程中,允许函数具有 side-effects,例如修改变量的值,写入文件,访问网络等。第二次运行相同的函数时,它可以检测先前的副作用并返回不同的内容。一个简单的 C 示例是:
int i = 0; int foo() { return i++; }
多次调用会生成不同的数字。另一个例子:
int foo(int *p) { return (*p)++; }
即使您使用相同的参数调用上述内容,i.e。相同的指针,由于增量,结果会有所不同。
在纯函数式编程中,设计禁止像i++这样的副作用。这样,函数的输出必须仅依赖于其参数的值。 E.g。如果在 Haskell 中我们有一个Int -> Int类型的函数f,我们可以确定f 3总是会在每次调用时返回相同的整数。
i++
Int -> Int
f
f 3
好吧,上述情况并非完全正确 - 一个程序最终必须做一些 I/O,否则就没有意义。因此,side-effects 必须以某种形式提供。在 Haskell 中,实际上允许带副作用的函数,但它们的类型必须告诉它们不是纯函数。 E.g。如果函数g具有类型Int -> IO Int,则它可以执行 I/O 并且具有 side-effects:运行print =<< g 3可以每次打印不同的值,就像在命令式编程中一样。
g
Int -> IO Int
print =<< g 3
因此,总结一下,在具有副作用的纯函数式编程函数中,与不具有副作用的函数具有不同的类型。通常,在 well-designed 功能程序中,很少需要 I/O 和 side-effects,纯函数构成绝大多数代码。因此,有时我们会说“纯函数编程禁止副作用”,因为大多数代码都是在这种约束条件下编写的。
纯函数式语言的操作使得相同的输入总是产生相同的输出,纯函数没有副作用。但是,在程序性或非纯函数式语言(如 C)中,可能会出现副作用,例如指针被修改,或其他外部因素,如时间或文件 I/O。即使 Haskell 也可以做文件 I/O。因此,如果带有 I/O 的 Haskell 是纯函数式语言,那么C 和 cpp 也是纯粹的功能。
以此 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。在任意数学之后,由于时间因素和睡眠,相同的输入在这个过程语言中不会产生相同的输出。
4242
get_num
一次运行,我们得到:
247 248 249 250 251 253 254 255 256 257
然后运行,我们得到:
270 271 272 273 274 275 277 278 279 280
在大多数 C 中,side-effects 比比皆是。但是,在纯函数式语言中,这样的 side-effects 将不存在或不可能。
为了进一步澄清,它不是一种语言的属性。没有命令式语言强迫您只编写副作用程序。尽管没有明确支持,但完全可以用纯函数样式编写。
而在纯函数式语言中,根本没有语言结构允许您修改变量,访问全局可见存储等。因此,有点夸张地说纯 FP 语言“禁止”不纯的函数 - 相反,没有首先写一个的方法。请注意,即使是以下功能:
printTwice x = do {putStrLn x; putStrLn x}
并不纯洁。应用printTwice会导致 IO 操作。你可以多次这样做,把结果放在列表或元组中并传递它们,这都是纯粹的。具体来说,两者之间没有区别:
printTwice
twoActions = (printTwice "hello", printTwice "hello")
和
twoActions = (a,a) where a = printTwice "hello"
3 回答
在命令式编程中,允许函数具有 side-effects,例如修改变量的值,写入文件,访问网络等。第二次运行相同的函数时,它可以检测先前的副作用并返回不同的内容。一个简单的 C 示例是:
多次调用会生成不同的数字。另一个例子:
即使您使用相同的参数调用上述内容,i.e。相同的指针,由于增量,结果会有所不同。
在纯函数式编程中,设计禁止像
i++
这样的副作用。这样,函数的输出必须仅依赖于其参数的值。 E.g。如果在 Haskell 中我们有一个Int -> Int
类型的函数f
,我们可以确定f 3
总是会在每次调用时返回相同的整数。好吧,上述情况并非完全正确 - 一个程序最终必须做一些 I/O,否则就没有意义。因此,side-effects 必须以某种形式提供。在 Haskell 中,实际上允许带副作用的函数,但它们的类型必须告诉它们不是纯函数。 E.g。如果函数
g
具有类型Int -> IO Int
,则它可以执行 I/O 并且具有 side-effects:运行print =<< g 3
可以每次打印不同的值,就像在命令式编程中一样。因此,总结一下,在具有副作用的纯函数式编程函数中,与不具有副作用的函数具有不同的类型。通常,在 well-designed 功能程序中,很少需要 I/O 和 side-effects,纯函数构成绝大多数代码。因此,有时我们会说“纯函数编程禁止副作用”,因为大多数代码都是在这种约束条件下编写的。
纯函数式语言的操作使得相同的输入总是产生相同的输出,纯函数没有副作用。但是,在程序性或非纯函数式语言(如 C)中,可能会出现副作用,例如指针被修改,或其他外部因素,如时间或文件 I/O。即使 Haskell 也可以做文件 I/O。因此,如果带有 I/O 的 Haskell 是纯函数式语言,那么C 和 cpp 也是纯粹的功能。
以此 C 程序为例:
我们将输入
4242
的常量参数设为get_num
。在任意数学之后,由于时间因素和睡眠,相同的输入在这个过程语言中不会产生相同的输出。一次运行,我们得到:
然后运行,我们得到:
在大多数 C 中,side-effects 比比皆是。但是,在纯函数式语言中,这样的 side-effects 将不存在或不可能。
为了进一步澄清,它不是一种语言的属性。没有命令式语言强迫您只编写副作用程序。尽管没有明确支持,但完全可以用纯函数样式编写。
而在纯函数式语言中,根本没有语言结构允许您修改变量,访问全局可见存储等。因此,有点夸张地说纯 FP 语言“禁止”不纯的函数 - 相反,没有首先写一个的方法。请注意,即使是以下功能:
并不纯洁。应用
printTwice
会导致 IO 操作。你可以多次这样做,把结果放在列表或元组中并传递它们,这都是纯粹的。具体来说,两者之间没有区别:和