纯函数:“无副作用”是否意味着“在给定相同输入的情况下始终相同的输出”?

Mag*_*nus 83 javascript functional-programming language-lawyer pure-function

定义函数的两个条件pure如下:

  1. 无副作用(即仅允许更改本地范围)
  2. 给定相同的输入,始终返回相同的输出

如果第一个条件始终为真,那么是否有第二次条件不为真?

即真的只有第一个条件才需要吗?

Ber*_*rgi 113

以下是一些不会改变外部范围但仍被认为是不纯的反例:

  • function a() { return Date.now(); }
  • function b() { return window.globalMutableVar; }
  • function c() { return document.getElementById("myInput").value; }
  • function d() { return Math.random(); } (这确实会更改PRNG,但不能视为可观察到的)

访问非常量的非局部变量足以违反第二个条件。

我一直认为纯净的两个条件是互补的:

  • 结果评估不得影响副状态
  • 评估结果不得旁态影响

术语“ 副作用”仅指第一个,该功能修改了非本地状态。但是,有时读操作也被视为副作用:当它们是操作且也涉及写入时,即使其主要目的是访问值。例如,生成伪随机数,该伪随机数会修改生成器的内部状态,从使读取位置前进的输入流中进行读取,或者从涉及“获取测量”命令的外部传感器中进行读取。

  • 如果“提示(“您选择”)没有副作用,我们应该退后一步,阐明副作用的含义。 (17认同)
  • 好吧,您知道吗Math.random()返回一个热敏二极管。实际未指定使用错误的RNG。 (2认同)

Jör*_*tag 29

表述纯函数是什么的“正常”方式是参照透明性。如果函数是参照透明的,则它是函数。

大致来说,参照透明性是指您可以在程序中的任何位置用其返回值替换对函数的调用,反之亦然,而无需更改程序的含义。

因此,例如,如果C printf是参照透明的,则这两个程序应具有相同的含义:

printf("Hello");
Run Code Online (Sandbox Code Playgroud)

5;
Run Code Online (Sandbox Code Playgroud)

并且以下所有程序应具有相同的含义:

5 + 5;

printf("Hello") + 5;

printf("Hello") + printf("Hello");
Run Code Online (Sandbox Code Playgroud)

因为printf返回写入的字符数,在这种情况下为5。

void功能变得更加明显。如果我有功能void foo,那么

foo(bar, baz, quux);
Run Code Online (Sandbox Code Playgroud)

应该与

;
Run Code Online (Sandbox Code Playgroud)

也就是说,既然foo什么都不返回,那么我应该能够在不改变程序含义的情况下将其替换为什么。

显然,然后,既不printf也不foo是引用透明,因此两者都不是纯的。实际上,void除非是非操作函数,否则它永远不可能是参照透明的。

我发现这个定义比您给的定义更容易处理。它还允许您以所需的任意粒度应用它:可以将其应用到单个表达式,函数或整个程序。例如,它允许您谈论这样的功能:

func fib(n):
    return memo[n] if memo.has_key?(n)
    return 1 if n <= 1
    return memo[n] = fib(n-1) + fib(n-2)
Run Code Online (Sandbox Code Playgroud)

我们可以分析组成函数的表达式,并容易得出结论,它们不是参照透明的,因此不是纯净的,因为它们使用可变的数据结构,即memo数组。但是,我们还可以查看该函数,并且可以看到它参照透明的,因此是纯函数。有时将其称为“ 外部纯净”,即对外部世界看似纯净的功能,但在内部不纯净地实现。

这样的功能仍然有用,因为尽管杂质会感染周围的所有东西,但外部纯接口会建立一种“纯度屏障”,其中杂质只会感染功能的三行,而不会泄漏到程序的其余部分。这三行比整个程序更容易分析正确性。

  • 并发后,该杂质会影响整个程序。 (2认同)

The*_*ght 12

在我看来,您描述的第二个条件比第一个条件弱。

让我举一个例子,假设您有一个函数可以添加一个也记录到控制台的函数:

function addOneAndLog(x) {
  console.log(x);
  return x + 1;
}
Run Code Online (Sandbox Code Playgroud)

您提供的第二个条件得到满足:给定相同的输入时,此函数始终返回相同的输出。但是,它不是纯粹的功能,因为它包括登录到控制台的副作用。

严格来讲,纯函数是满足参照透明性的函数。这就是我们可以用函数产生的值替换函数应用程序的属性,而无需更改程序的行为。

假设我们有一个简单地添加以下功能的函数:

function addOne(x) {
  return x + 1;
}
Run Code Online (Sandbox Code Playgroud)

我们可以在程序中的任何地方替换它addOne(5)6并且什么都不会改变。

相比之下,我们不能在不更改行为的情况下用程序中的任何位置替换addOneAndLog(x)6,因为第一个表达式导致某些内容被写入控制台,而第二个表达式则没有。

我们认为addOneAndLog(x)除了返回输出以外,任何这种额外的行为都会产生副作用

  • 似乎您使用的“副作用”定义与常用定义不同。副作用通常被定义为“除返回值外的可观察的结果”或“状态的可观察的变化”-参见例如[Wikipedia](https://en.wikipedia.org/wiki/Side_effect_(computer_science)),[这篇关于softwareengineering.SE的帖子(https://softwareengineering.stackexchange.com/questions/40297/what-is-a-side-effect)。完全正确的是,“ Date.now()”不是纯净/参照透明的,但这不是因为它具有副作用,而是因为其结果不仅仅取决于其输入。 (2认同)

use*_*459 7

系统外部可能存在随机性。假设计算的一部分包括室温。然后,根据室温的随机外部因素,每次执行该功能都会产生不同的结果。通过执行程序不会更改状态。

无论如何,我所能想到的。

  • 据我说,这些“来自系统外部的随机性”是一种副作用。具有这些行为的功能不是“钱包”。 (3认同)