以下函数是纯函数吗?
function test(min,max) {
return Math.random() * (max - min) + min;
}
我的理解是,纯函数遵循以下条件:
- 它返回从参数计算出的值
- 除了计算返回值之外,它不执行任何其他工作
如果这个定义正确,那么我的函数是一个纯函数吗?或者说,我对定义纯函数的理解是错误的吗?
以下函数是纯函数吗?
function test(min,max) {
return Math.random() * (max - min) + min;
}
我的理解是,纯函数遵循以下条件:
如果这个定义正确,那么我的函数是一个纯函数吗?或者说,我对定义纯函数的理解是错误的吗?
function test(random, min, max) {
return random * (max - min) + min;
}
然后这样调用它(例如,使用2和5作为最小值和最大值):
test( Math.random(), 2, 5)
Math.random
之前在函数内部重新播种随机生成器会怎样? - cs95Math.random
输出);要使其纯净,您需要以某种方式保存当前的 RNG 状态,重新生成种子,调用 Math.random
,然后将其恢复到先前的状态。 - LegionMammal978简单回答您的问题是,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
不使用伪随机数生成器而是使用硬件随机数生成器实现,那么这个答案会有什么变化呢?硬件随机数生成器在正常情况下并没有状态,但它确实产生随机值(因此函数的输出仍然与输入无关),对吗? - mtraceur从数学角度来看,你的签名并不是
test: <number, number> -> <number>
但是
test: <environment, number, number> -> <environment, number>
在这里,环境
能够提供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
的状态也可能已经改变了!
test:<环境,数字,数字>→<环境,数字>
。 - Bergia.F(b, c)
可以看作是对F(a, b, c)
的语法糖,有一个特殊规则来根据a
的类型分配到重载定义的F
(这实际上是Python的表示方式)。但是,不管哪种写法,a
都是显式的,而在非纯函数中,环境从未在源代码中提到。 - IMSoP纯函数总是针对相同的输入返回相同的值。 纯函数是可预测的并且具有引用透明性,这意味着我们可以用返回的输出替换函数调用,而不会改变程序的工作方式。
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);
util.Random
的同时生成可重复的测试,您可以在测试运行开始时对其进行种子处理以重复旧行为或进行新的(但可重复的)运行。如果涉及多线程,则可以在主线程中执行此操作并使用该Random
来种子可重复的线程本地Random
。但是,据我所知,test(int,int,Random)
不被认为是纯净的,因为它改变了Random
的状态。 - PJTraill您是否愿意接受以下内容:
return ("" + test(0,1)) + test(0,1);
等同于
var temp = test(0, 1);
return ("" + temp) + temp;
你知道,纯函数的定义是其输出仅与输入相关。如果我们说JavaScript有一种方法可以标记一个函数为纯函数,并利用它,那么优化器将被允许将第一个表达式重写为第二个表达式。
我在实践中有过这方面的经验。SQL Server允许在“纯”函数中使用getdate()
和newid()
,并且优化器会根据需要去重调用。但有时这会做出一些愚蠢的事情。
Math.random()
方法,这会改变随机数生成器的状态。 - Paul Drapertest(a,b)
总是返回相同的对象Random(a,b)
(它可以表示不同的具体数字)?如果保持Random
符号,则在经典意义上是纯的,如果您早期评估并输入数字,可能作为一种优化,该函数仍然保留一些“纯度”。 - jdm