定义函数的两个条件pure
如下:
- 无副作用(即仅允许更改本地范围)
- 给定相同的输入,始终返回相同的输出
如果第一个条件始终为真,那么是否有第二次条件不为真?
即真的只有第一个条件才需要吗?
定义函数的两个条件pure
如下:
如果第一个条件始终为真,那么是否有第二次条件不为真?
即真的只有第一个条件才需要吗?
Answers:
以下是一些不会改变外部范围但仍被认为是不纯的反例:
function a() { return Date.now(); }
function b() { return window.globalMutableVar; }
function c() { return document.getElementById("myInput").value; }
function d() { return Math.random(); }
(这确实会更改PRNG,但不能视为可观察到的)访问非常量的非局部变量足以违反第二个条件。
我一直认为纯净的两个条件是相辅相成的:
术语“副作用”仅指第一种,该功能修改了非局部状态。但是,有时读操作也被视为副作用:当它们是操作且也涉及写入时,即使其主要目的是访问值。例如,生成伪随机数来修改生成器的内部状态,从推进读取位置的输入流中读取数据,或者从涉及“获取测量”命令的外部传感器读取数据。
prompt("you choose")
没有副作用,我们应该退后一步,阐明副作用的含义。
表述纯函数是什么的“正常”方式是参照透明性。如果函数是参照透明的,则它是纯函数。
大致来说,参照透明性是指您可以在程序中的任何位置用其返回值替换对函数的调用,反之亦然,而无需更改程序的含义。
因此,例如,如果Cprintf
是参照透明的,则这两个程序应具有相同的含义:
printf("Hello");
和
5;
并且以下所有程序应具有相同的含义:
5 + 5;
printf("Hello") + 5;
printf("Hello") + printf("Hello");
因为printf
返回写入的字符数,在这种情况下为5。
void
功能变得更加明显。如果我有功能void foo
,那么
foo(bar, baz, quux);
应该与
;
即既然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)
我们可以分析组成函数的表达式,并容易得出结论,它们不是参照透明的,因此不是纯净的,因为它们使用可变的数据结构,即memo
数组。但是,我们还可以查看该函数,并且可以看到它是参照透明的,因此是纯函数。有时将其称为外部纯净,即,对外部世界看似纯净的功能,但在内部却不纯净地实现。
这样的功能仍然有用,因为当杂质感染周围的所有东西时,外部纯接口会建立一种“纯度屏障”,其中杂质只会感染功能的三行,而不会泄漏到程序的其余部分。这三行比整个程序更容易分析正确性。
memo[n]
是幂等的,而从中读取数据只会浪费CPU周期。
memo[n] = ...
可以首先创建一个字典条目,然后将值存储到其中。这就留下了一个窗口,在此期间另一个线程可以看到未初始化的条目。
在我看来,您描述的第二个条件比第一个条件弱。
让我举一个例子,假设您有一个函数可以添加一个也记录到控制台的函数:
function addOneAndLog(x) {
console.log(x);
return x + 1;
}
您提供的第二个条件得到满足:给定相同的输入时,此函数始终返回相同的输出。但是,它不是纯粹的功能,因为它包括登录到控制台的副作用。
严格来讲,纯函数是满足参照透明性的函数。这就是我们可以用其产生的值替换功能应用程序的属性,而无需更改程序的行为。
假设我们有一个简单地添加以下功能的函数:
function addOne(x) {
return x + 1;
}
我们可以在程序中的任何地方替换它addOne(5)
,6
并且什么都不会改变。
相比之下,我们不能在不更改行为的情况下用程序中的任何位置替换addOneAndLog(x)
值6
,因为第一个表达式导致某些内容被写入控制台,而第二个表达式则没有。
我们认为addOneAndLog(x)
除了返回输出以外,任何这种额外的行为都会产生副作用。
Date.now()
它不是纯粹/相对透明的,但这不是因为它具有副作用,而是因为其结果不仅取决于其输入。
FP定义的问题在于它们是非常人为的。每次评估/计算都会对评估者产生副作用。从理论上讲是正确的。对此的否认仅表明FP辩护者无视哲学和逻辑:“评估”意味着改变某些智能环境(机器,大脑等)的状态。这就是评估过程的本质。没有变化-没有“结石”。效果非常明显:加热CPU或其故障,以防过热而关闭主板,等等。
当您谈论引用透明性时,您应该理解有关透明性的信息对于整个系统的创建者和语义信息的持有者都是人类可用的,而编译器可能不可用。例如,一个函数可以读取一些外部资源,并且其签名中将包含IO monad,但始终会返回相同的值(例如的结果current_year > 0
)。编译器不知道该函数将始终返回相同的结果,因此该函数不纯,但具有参照透明属性,可以用True
常量替换。
因此,为避免这种不准确性,我们应该区分数学函数和编程语言中的“函数”。Haskell中的函数始终是不纯的,与它们相关的纯度的定义始终是有条件的:它们在具有真实副作用和物理特性的真实硬件上运行,这对于数学函数是错误的。这意味着带有“ printf”功能的示例是完全错误的。
但是并不是所有的数学函数也都是纯函数:每个以t
(时间)为参数的函数可能都是不纯净的:t
具有函数的所有作用和随机性:在通常情况下,您有输入信号,却不知道实际值,甚至成为噪音。
如果第一个条件始终为真,那么是否有第二次条件不为真?
是
考虑下面的简单代码段
public int Sum(int a, int b) {
Random rnd = new Random();
return rnd.Next(1, 10);
}
对于相同的给定输入集,此代码将返回随机输出-但是它没有任何副作用。
您提到的两个点#1和#2的总体效果是:在任何时候,如果将Sum
具有相同i / p的函数替换为程序中的结果,则程序的整体含义不变。这不过是参照透明。
rnd
不会转义该函数,因此其状态改变的事实与该函数的纯度无关紧要,但是Random
构造函数使用当前时间作为种子值的事实意味着除a
和之外还有“输入” b
。