以下是纯函数吗?
1 2 3
| function test(min,max) {
return Math.random() * (max - min) + min;
} |
我的理解是纯函数遵循以下条件:
它返回根据参数计算的值
除了计算返回值外,它不做任何工作
如果这个定义正确,我的函数是纯函数吗?或者我对纯函数定义的理解是错误的?
- "除了计算返回值之外,它不做任何工作",但它调用Math.random(),这会改变RNG的状态。
- 第二点更像是"它不改变外部(对函数)状态";第一点应该有点补充,就像"它返回从相同参数计算出的相同值",就像人们在下面写的那样
- 是否存在允许随机性的半纯函数的概念?例如,test(a,b)总是返回相同的对象Random(a,b)(可以表示不同的具体数字)?如果你保持Random的符号化,它在经典意义上是纯的,如果你早点评估它并放入数字,也许作为一种优化,函数仍然保持一些"纯"。
- "任何考虑用算术方法产生随机数字的人,当然是处于犯罪状态。"—约翰·冯·诺依曼
- @JDM如果它返回相同的随机对象,而不是随机数,那么它是纯的
- @JDM如果您遵循"半纯"的思路,在这里您将函数纯模块视为一些定义良好的副作用,那么您可能最终会发明monad。欢迎来到黑暗面。>)
- @不可比性我们需要一个不可变的随机对象,它将返回一个元组(newrandom,randomnumber),然后我们必须从这个函数返回这个元组才能使它有用。如果有点奇怪的话似乎是可行的。
- @VOO在这种情况下是纯的,但是纯并不是很有用,因为你每次都要传递一个不同的随机对象。
- @immibis在JavaScript中使用肯定很难,但是您可以将其作为非纯函数使用,只需传递状态(这就是函数编程的一般工作方式)。
不,不是。如果输入相同,此函数将返回不同的值。然后,您就不能构建一个映射输入和输出的"表"。
从维基百科的纯函数文章:
The function always evaluates the same result value given the same
argument value(s). The function result value cannot depend on any
hidden information or state that may change while program execution
proceeds or between different executions of the program, nor can it
depend on any external input from I/O devices
号
另外,另一件事是纯函数可以替换为表示输入和输出映射的表,如本线程中所述。
如果要重写此函数并将其更改为纯函数,还应将随机值作为参数传递
1 2 3
| function test(random, min, max) {
return random * (max - min) + min;
} |
然后这样称呼它(例如,最小值和最大值分别为2和5):
1
| test( Math.random(), 2, 5) |
号
- 如果在调用Math.random之前,每次在函数内部重新设定随机生成器种子,会怎么样?
- @ C????S????即使这样,它仍然会有副作用(改变未来的Math.random输出);为了使其纯净,您必须以某种方式保存当前的rng状态,重新设置它,调用Math.random并将其恢复到以前的状态。
- @ C????S?????所有计算的RNG都是基于伪随机性。某些东西必须在下面运行,导致它看起来随机,而你不能解释这一点,使它不纯洁。而且,也许对你的问题更重要,你不能随机输入数学。
- @哺乳动物军团978……而且是原子性的。
- @ C????S?????:您可以考虑接受一个lang.util.Random实例,尽管它的状态当然会改变,使其不纯洁,但允许调用者控制受影响的内容。此外,就我所见,EDCOX1的3个词是Java,而不是JavaScript。
- @ C????S?????:…或者是util/concurrent/ThreadLocalRandom或util/security/SecureRandom的实例(实际上是子类)。
- 从使用RNG的函数中生成纯函数的另一种方法是在RNG状态下传入和传出函数:function test(rng, min, max) { [rng, rand] = next(rng); return [rng, rand * (max - min) + min]; }。
- PJTraill在Java中也没有EDCOX1 3。也许你是说java.util.Random?同样,这个问题是关于JavaScript,而不是Java,它们在这里是无关的。
- @艾斯:谢谢,我当然是指埃多克斯。我认为这值得一提,因为您可以在JavaScript中做或使用类似的事情。
- @利瑞安:正如我在维基百科上看到的"纯粹"的定义,改变一个通过的论点的状态也是不允许的。
- @pjtrail:您在哪里看到我的函数版本更改了传递参数的状态?
- @ C????S?????有一些方法可以让RNG与纯函数一起运行,但它涉及将RNG状态传递给函数,并让函数返回替换的RNG状态,这就是haskell(一种强制实现函数纯性的函数编程语言)实现它的方法。
- @Lieryan:我想next(rng)会改变rng的状态,但我承认我不知道next是什么功能——这是一个库函数(我找不到)还是你自己的,我在哪里可以找到它的规范?
- @pjtrail:next()是RNG的一部分,在给定RNG状态rng的情况下,返回一个包含下一个RNG状态rng'和RNG中下一个随机数的元组。在代码[rng1, a] = next(rng); [rng2, b] = next(rng)中,rng1 == rng2和a == b总是这样。要在prng中获得下一个数字,您需要这样做:[rng1, a] = next(rng); [rng2, b] = next(rng1)。不过,在类似haskell的函数语言中,您可能会在这里使用monad,以避免在任何地方显式地传递rng状态。
- @pjtraill:注意,对于next()和test()来说,RNG不一定是伪RNG,为了保持上述实现的纯粹性,它可能是一个真正的(硬件)RNG,您可以用纯语言进行IO的方式来处理它。
你问题的简单答案是,Math.random()违反了规则2。
这里还有许多其他的答案指出,Math.random()的存在意味着这个函数并不纯粹。但我认为值得一提的是,为什么Math.random()会污染使用它的函数。
和所有伪随机数生成器一样,Math.random()以"seed"值开头。然后,它使用该值作为一系列低级位操作或其他操作的起点,这些操作会导致不可预测(但不是真正随机)的输出。
在javascript中,所涉及的过程依赖于实现,与许多其他语言不同,javascript无法选择种子:
The implementation selects the initial seed to the random number generation algorithm; it cannot be chosen or reset by the user.
号
这就是为什么这个函数不是纯函数:JavaScript本质上使用的是一个您无法控制的隐式函数参数。它从其他地方计算和存储的数据中读取该参数,因此违反了定义中的规则2。
如果你想让它成为一个纯函数,你可以使用这里描述的另一个随机数生成器。叫那个发电机seedable_random。它接受一个参数(种子)并返回一个"随机"数字。当然,这个数字根本不是随机的,它是由种子决定的。这就是为什么这是一个纯函数。seedable_random的输出只是"随机的",从这个意义上说,根据输入预测输出是困难的。
此函数的纯版本需要采用三个参数:
1 2 3
| function test(min, max, seed) {
return seedable_random(seed) * (max - min) + min;
} |
对于任何给定的三个(min, max, seed)参数,这将始终返回相同的结果。
请注意,如果您希望seedable_random的输出是真正随机的,那么您需要找到一种方法来随机化种子!无论您使用什么策略,都不可避免地是非纯策略,因为它需要您从您的功能之外的源收集信息。正如Mtraceur和JPMC26提醒我的那样,这包括所有的物理方法:硬件随机数发生器、带镜头盖的摄像头、大气噪声收集器,甚至熔岩灯。所有这些都涉及到使用在函数外部计算和存储的数据。
- random()不仅读取它的"seed",还修改它,以便下次调用返回不同的结果。根据和修改,静态对于纯函数来说肯定是不好的。
- @Nateel Dredge,相当不错!虽然简单地读取一个依赖于实现的值就足以破坏纯度。例如,有没有注意到在进程之间python 3散列是如何不稳定的?
- 如果Math.random不使用prng,而是使用硬件rng来实现,那么这个答案将如何改变?硬件RNG在正常意义上并没有真正的状态,但它确实会产生随机值(因此,不管输入如何,函数输出仍然是不同的),对吗?
- @Mtraceur,没错。但我认为答案不会改变太多。事实上,这就是为什么我不花时间在我的答案中谈论"状态"的原因。从硬件RNG中读取也意味着从"其他地方计算和存储的数据"中读取。这只是在数据与环境交互时计算并存储在计算机本身的物理介质中。
- @Mtraceur,再多想一想,我发现虽然我仍然认为我的答案不会改变,但是在硬件RNG的情况下,上面规则2中的"做工作"的定义必须仔细指定。也许"它不做任何工作"应该是"它不依赖于任何已做的工作"。
- @或者有一个基于qm的论点:当我们读取一个随机值时,我们通过量子退相干机制引起世界状态的改变。状态改变就是完成的工作。
- 基于输入预测输出一点也不困难(毕竟,有一些代码实现了这一点)。
- @ Pa?Loebermann,我知道"预测"是多么的不正确。但是散列函数通常是这样描述的,你所说的对它们也是正确的,不是吗?
- 同样的逻辑甚至适用于更复杂的随机方案,甚至是像random.org的大气噪声这样的方案。+ 1
- @nateeldrege RNG算法未指定,RNG不能重新植入,因此无法观察是否实际发生了Math.random调用。因此,在纯函数中调用Math.random并丢弃结果是可以的。您不能通过用返回值替换对该纯函数的调用来区分是否存在对Math.random的调用,因为这样做时,您需要一个新的执行,它有一个新的种子。
纯函数是一个函数,其中返回值仅由其输入值确定,没有明显的副作用。
通过使用math.random,您可以通过输入值以外的其他值来确定其值。它不是纯函数。
来源
不,它不是纯函数,因为它的输出不仅依赖于提供的输入(math.random()可以输出任何值),而纯函数应该始终为相同的输入输出相同的值。
如果一个函数是纯函数,那么优化多个具有相同输入的调用是安全的,只需重用先前调用的结果。
至少对我和其他许多人来说,redux使纯函数这个词很流行。直接从Redux文档:
Things you should never do inside a reducer:
-
Mutate its arguments;
-
Perform side effects like API calls and routing transitions;
-
Call non-pure functions, e.g. Date.now() or Math.random().
号
- 虽然其他人提供了很好的答案,但当Redux Doc出现在我的脑海中时,我无法抗拒自己和其中特别提到的math.random()。
从数学的角度看,你的签名不是
1
| test: <number, number> -> <number> |
号
但是
1
| test: <environment, number, number> -> <environment, number> |
其中,environment能够提供Math.random()的结果。实际上,生成随机值会使环境发生变化,这是一个副作用,所以您还返回一个新的环境,这不等于第一个环境!
换句话说,如果您需要任何不是来自初始参数的输入(部分),那么需要为您提供执行环境(在本例中,该环境为Math提供状态)。这同样适用于其他答案提到的其他事情,如I/O或类似的。
作为一个类比,您也可以注意到,这就是如何表示面向对象的编程——如果我们说,例如。
1 2
| SomeClass something
T result = something.foo(x, y) |
。
实际上我们正在使用
1
| foo: <something: SomeClass, x: Object, y: Object> -> <SomeClass, T> |
使其方法被调用的对象成为环境的一部分。为什么SomeClass是结果的一部分?因为something的状态也可能改变!
- 更糟的是,环境也发生了变化,所以test: -> 应该是
- @是的,你绝对是对的!修正。
- 我不确定OO示例是否非常相似。a.F(b, c)可以看作是F(a, b, c)的语法糖,根据a的类型(这实际上是python表示它的方式),有一个特殊的规则来发送F的重载定义。但是在这两个符号中,a仍然是显式的,而源代码中从未提到非纯函数中的环境。
纯函数总是为相同的输入返回相同的值。纯函数是可预测的,并且是引用透明的,这意味着我们可以用返回的输出替换函数调用,并且它不会改变程序的工作。
https://github.com/mostly adequate/mostly-sufficient-guide/blob/master/ch3.md网站
除了正确指出该函数是如何非确定性的其他答案外,它还有一个副作用:它将导致将来调用Math.random()返回不同的答案。而一个没有这个属性的随机数生成器通常会执行某种类型的I/O,比如从操作系统提供的随机设备中读取。两者都是纯函数的原语。
不,不是的。你根本不知道结果,所以这段代码无法测试。要使该代码可测试,需要提取生成随机数的组件:
1 2 3
| function test(min, max, generator) {
return generator() * (max - min) + min;
} |
现在,您可以模拟生成器并正确测试代码:
1 2
| const result = test(1, 2, () => 3);
result == 4 //always true |
。
在您的"生产"代码中:
1
| const result = test(1, 2, Math.random); |
- 你对可测试性的思考。稍微小心一点,您还可以在接受util.Random的同时生成可重复的测试,您可以在测试运行开始时种子,以重复旧的行为或新的(但可重复的)运行。如果是多线程,您可以在主线程中执行此操作,并使用该Random来种子可重复的线程本地Random。但是,据我所知,test(int,int,Random)不被认为是纯的,因为它会改变Random的状态。
您能接受以下条件吗?
1
| return ("" + test(0,1)) + test(0,1); |
。
相当于
1 2
| var temp = test(0, 1);
return ("" + temp) + temp; |
。
?
你看,pure的定义是一个函数,它的输出除了它的输入外没有任何变化。如果我们说javascript有一种方法可以标记一个函数pure并利用它,优化器将允许将第一个表达式重写为第二个表达式。
我在这方面有实际经验。SQL Server允许getdate()和newid()使用"pure"函数,优化器将自动消除调用。有时这会做一些愚蠢的事情。