以下是纯函数吗?
function test(min,max) {
return Math.random() * (max - min) + min;
}
我的理解是纯函数遵循以下条件:
- 它返回从参数计算的值
- 除了计算返回值之外,它不做任何工作
如果这个定义是正确的,我的函数是纯函数吗?还是我对定义纯函数的理解不正确?
以下是纯函数吗?
function test(min,max) {
return Math.random() * (max - min) + min;
}
我的理解是纯函数遵循以下条件:
如果这个定义是正确的,我的函数是纯函数吗?还是我对定义纯函数的理解不正确?
不,这不对。给定相同的输入,此函数将返回不同的值。然后你不能建立一个映射输入和输出的“表”。
来自纯函数的维基百科文章:
给定相同的参数值,该函数始终评估相同的结果值。函数结果值不能依赖于在程序执行过程中或程序的不同执行之间可能发生变化的任何隐藏信息或状态,也不能依赖于来自 I/O 设备的任何外部输入
此外,另一件事是纯函数可以替换为表示输入和输出映射的表,如该线程中所述。
如果要重写此函数并将其更改为纯函数,则也应将随机值作为参数传递
function test(random, min, max) {
return random * (max - min) + min;
}
然后以这种方式调用它(例如,以 2 和 5 作为最小值和最大值):
test( Math.random(), 2, 5)
您的问题的简单答案是Math.random()
违反规则#2。
这里的许多其他答案都指出,存在Math.random()
意味着这个函数不是纯粹的。但我认为值得一提的是,为什么 Math.random()
会污染使用它的函数。
像所有伪随机数生成器一样,Math.random()
以“种子”值开头。然后,它使用该值作为一系列低级位操作或其他操作的起点,这些操作会导致不可预测的(但不是真正随机的)输出。
在 JavaScript 中,所涉及的过程是依赖于实现的,并且与许多其他语言不同,JavaScript没有提供选择种子的方法:
实现选择随机数生成算法的初始种子;用户不能选择或重置它。
这就是为什么这个函数不是纯粹的:JavaScript 本质上是使用一个你无法控制的隐式函数参数。它从其他地方计算和存储的数据中读取该参数,因此违反了您定义中的规则 #2。
如果您想让它成为一个纯函数,您可以使用此处描述的替代随机数生成器之一。调用那个生成器seedable_random
。它接受一个参数(种子)并返回一个“随机”数。当然,这个数字根本不是随机的。它是由种子唯一决定的。这就是为什么这是一个纯函数。的输出seedable_random
只是“随机”的,因为根据输入预测输出是困难的。
这个函数的纯版本需要三个参数:
function test(min, max, seed) {
return seedable_random(seed) * (max - min) + min;
}
对于任何给定的三元组(min, max, seed)
参数,这将始终返回相同的结果。
请注意,如果您希望 的输出seedable_random
真正随机,则需要找到一种方法来随机化种子!你使用的任何策略都不可避免地是不纯的,因为它需要你从你的职能之外的来源收集信息。正如mtraceur和jpmc26提醒我的那样,这包括所有物理方法:硬件随机数生成器、带镜头盖的网络摄像头、大气噪声收集器——甚至是熔岩灯。所有这些都涉及使用在函数外部计算和存储的数据。
不,它不是纯函数,因为它的输出不仅仅取决于提供的输入(Math.random() 可以输出任何值),而纯函数应该始终为相同的输入输出相同的值。
如果一个函数是纯函数,则可以安全地优化具有相同输入的多个调用并重用先前调用的结果。
PS 至少对我和其他许多人来说,redux 使纯函数这个术语变得流行。直接来自 redux 文档:
在 reducer 中你不应该做的事情:
改变它的论点;
执行 API 调用和路由转换等副作用;
调用非纯函数,例如 Date.now() 或 Math.random()。
从数学的角度来看,您的签名不是
test: <number, number> -> <number>
但
test: <environment, number, number> -> <environment, number>
其中environment
能够提供 的结果Math.random()
。并且实际上生成随机值会改变环境作为副作用,所以你也返回一个新的环境,它不等于第一个!
换句话说,如果您需要任何类型的输入不是来自初始参数(<number, number>
部分),那么您需要提供执行环境(在此示例中为 提供状态Math
)。这同样适用于其他答案提到的其他事情,如 I/O 等。
作为一个类比,您还可以注意到这就是面向对象编程的表示方式 - 如果我们说,例如
SomeClass something
T result = something.foo(x, y)
那么实际上我们正在使用
foo: <something: SomeClass, x: Object, y: Object> -> <SomeClass, T>
调用其方法的对象是环境的一部分。为什么SomeClass
是结果部分?因为something
的状态也可能发生了变化!
纯函数总是为相同的输入返回相同的值。纯函数是可预测的并且是引用透明的,这意味着我们可以用返回的输出替换函数调用,并且不会改变程序的工作。
https://github.com/MostlyAdequate/mostly-adequate-guide/blob/master/ch3.md
除了正确指出此函数如何不确定的其他答案之外,它还有一个副作用:它将导致未来的调用math.random()
返回不同的答案。不具有该属性的随机数生成器通常会执行某种 I/O,例如从操作系统提供的随机设备中读取。对于纯函数,要么是禁止的。
不,不是。你根本想不通结果,所以这段代码无法测试。为了使该代码可测试,您需要提取生成随机数的组件:
function test(min, max, generator) {
return generator() * (max - min) + min;
}
现在,您可以模拟生成器并正确测试您的代码:
const result = test(1, 2, () => 3);
result == 4 //always true
在您的“生产”代码中:
const result = test(1, 2, Math.random);
您是否可以接受以下内容:
return ("" + test(0,1)) + test(0,1);
相当于
var temp = test(0, 1);
return ("" + temp) + temp;
?
你看,pure 的定义是一个函数,它的输出除了输入之外没有任何变化。如果我们说 JavaScript 有办法标记纯函数并利用这一点,那么优化器将被允许将第一个表达式重写为第二个。
我有这方面的实践经验。SQL 服务器允许getdate()
并newid()
在“纯”函数中,优化器将随意重复调用。有时这会做一些愚蠢的事情。