-2

我最近在一次采访中被问到这个问题。我无法得到正确的答案。

当您在程序语言(如 C)和函数式语言(如 haskell)中重复调用具有相同参数的函数时,在哪种语言中您可能会得到不同的结果?我在 [this](过程编程和函数式编程之间有什么区别?)上读到纯函数式语言总是会得到相同的答案。为什么对于函数式语言而不是过程语言如此?

4

3 回答 3

3

纯函数式语言的运行方式是相同的输入总是产生相同的输出,纯函数没有副作用。但是,在过程性或非纯函数式语言(例如 C)中,可能会出现副作用,例如指针被修改,或其他外部因素(例如时间或文件 I/O)。甚至 Haskell 也可以进行文件 I/O。因此,如果带 I/O 的 Haskell 是纯函数式语言,那么C 和 thecpp也是纯函数式的

以这个 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 语言中,副作用比比皆是。但是,在纯函数式语言中,不会出现或不可能出现这种副作用。

于 2015-05-17T05:18:53.167 回答
3

在命令式编程中,函数是允许有副作用的,比如修改变量的值、写入文件、访问网络等。第二次运行同一个函数,它可以检测到之前的副作用并返回一些东西不同的。一个简单的 C 示例是:

int i = 0;
int foo() {
  return i++;
}

多次调用它会产生不同的数字。再举一个例子:

int foo(int *p) {
  return (*p)++;
}

即使你用相同的参数,即相同的指针调用上面的,结果也会因为增量而不同。

函数式编程中,诸如此类的副作用i++是设计禁止的。这样,函数的输出必须仅取决于其参数的值。例如,如果在 Haskell 中我们有一个ftype的函数Int -> Int,我们可以确定它f 3在每次调用时总是返回相同的整数。

好吧,以上内容并不完全正确——程序最终必须进行一些 I/O,否则将毫无意义。因此,必须以某种形式提供副作用。在 Haskell 中,实际上允许具有副作用的函数,但它们的类型必须表明这些不是纯函数。例如,如果函数g有 type Int -> IO Int,那么它可以执行 I/O 并有副作用:运行print =<< g 3每次都可以打印不同的值,就像在命令式编程中一样。

因此,总而言之,在纯函数式编程中,具有副作用的函数与没有副作用的函数具有不同的类型。通常,在设计良好的函数式程序中,很少需要 I/O 和副作用,而纯函数构成了绝大多数代码。正因为如此,有时我们说“纯函数式编程禁止副作用”,因为大部分代码都是在这种约束下编写的。

于 2015-05-17T09:25:37.927 回答
1

为了进一步澄清,它并不是一种语言的属性。没有任何命令式语言会强迫您只编写具有副作用的过程。尽管没有明确支持,但也完全可以用纯函数式编写。

而在函数式语言中,根本没有允许您修改变量、访问全局可见存储等的语言结构。因此,说纯 FP 语言“禁止”不纯函数有点夸张 - 相反,没有首先写一个的方法。请注意,即使功能如下:

printTwice x = do {putStrLn x; putStrLn x}

不是不纯的。printTwice在 IO 操作中应用结果。您可以多次这样做,将结果放入列表或元组中并传递它们,这都是纯粹的。具体来说,两者之间没有区别:

 twoActions = (printTwice "hello", printTwice "hello")

 twoActions = (a,a) where a = printTwice "hello"
于 2015-05-17T11:02:32.237 回答