调用Math.random()的函数是否纯净?


112

以下是纯函数吗?

function test(min,max) {
   return  Math.random() * (max - min) + min;
}

我的理解是纯函数遵循以下条件:

  1. 它返回从参数计算得出的值
  2. 除了计算返回值外,它不做任何其他工作

如果这个定义正确,那么我的函数是纯函数吗?还是我对定义纯函数的理解不正确?


66
“除了计算返回值外,它没有做任何其他工作”,但是它调用Math.random()了RNG的状态更改。
Paul Draper

1
第二点更像是“它不会改变外部(功能)状态”。并且第一个应该像“有人返回从SAME参数计算出的SAME值”那样进行补充,就像人们在下面这样写道
MVCDS

是否有半纯函数的概念,允许随机性?例如,test(a,b)总是返回相同的对象Random(a,b)(可以表示不同的具体数字)?如果保留Random符号,则它在古典意义上是纯正的,如果您对其进行早期评估并输入数字,也许作为一种优化,该函数仍会保留一些“纯净性”。
jdm '17

1
“任何考虑产生随机数的算术方法的人当然都处于犯罪状态。” -约翰·冯·诺依曼(John von Neumann)
Steve Kuo)

1
@jdm如果遵循“半纯”的线程,在这种情况下,您认为函数只是对某些明确定义的副作用进行模运算,那么您可能最终发明了monad。欢迎来到黑暗的一面。> :)
luqui

Answers:


185

不,这不对。给定相同的输入,此函数将返回不同的值。然后,您将无法建立映射输入和输出的“表”。

来自Wikipedia的Pure函数文章:

给定相同的参数值,该函数始终求值相同的结果值。函数结果值不能取决于在程序执行过程中或在程序的不同执行之间可能更改的任何隐藏信息或状态,也不能取决于I / O设备的任何外部输入

另外,另一件事是,纯函数可以用表代替,该表代表来自输入和输出的映射,如本线程所述

如果要重写此函数并将其更改为纯函数,则也应将随机值作为参数传递

function test(random, min, max) {
   return random * (max - min) + min;
}

然后以这种方式进行调用(例如,最小和最大分别为2和5):

test( Math.random(), 2, 5)

2
如果您每次在调用前在函数内重新种子随机生成器Math.random怎么办?
cs95

16
@cᴏʟᴅsᴘᴇᴇᴅ即使那样,它仍然会有副作用(改变将来的Math.random输出);为了使它纯净,您必须以某种方式保存当前的RNG状态,重新设定种子状态,调用Math.random,并将其还原到以前的状态。
LegionMammal978

2
@cᴏʟᴅsᴘᴇᴇᴅ所有计算的RNG都是基于伪造随机性。某些东西必须在下面运行,导致它看起来是随机的,而您无法解释这是不纯的。此外,可能对您的问题更重要,您无法
播出

14
@ LegionMammal978…并且原子地这样做。
wchargin

2
@cᴏʟᴅsᴘᴇᴇᴅ有一些方法可以使RNG可以通过纯函数运行,但是涉及将RNG状态传递给函数,并使函数返回替换RNG状态,这是Haskell(强制执行函数纯度的函数编程语言)如何实现的它。
法拉普

50

您问题的简单答案是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真正随机的,则需要找到一种方法来随机化种子!而且,无论您使用哪种策略都不可避免地是不纯正的,因为这将需要您从功能之外的来源收集信息。正如mtraceurjpmc26提醒我的那样,这包括所有物理方法:硬件随机数生成器带镜头盖的网络摄像头大气噪声收集器 -甚至熔岩灯。所有这些都涉及使用在函数外部计算和存储的数据。


8
Math.random()不仅读取其“种子”,还对其进行修改,以便下一次调用将返回不同的内容。依赖并修改静态状态绝对对纯函数不利。
Nate Eldredge

2
@NateEldredge,相当!尽管仅读取依赖于实现的值就足以破坏纯度。例如,您是否注意到Python 3哈希在进程之间不稳定吗?
senderle '17

2
如果Math.random不使用PRNG而是使用硬件RNG来实现,此答案将如何改变?硬件RNG并没有真正意义上的状态,但是它确实会产生随机值(因此,不管输入如何,函数输出仍然是不同的),对吗?
mtraceur '17

@mtraceur,没错。但是我认为答案不会有太大变化。实际上,这就是为什么我不花时间在答案中谈论“状态”的原因。从硬件RNG读取还意味着从“计算和存储在其他位置的数据”读取。只是数据在与环境交互时被计算并存储在计算机本身的物理介质中。
senderle '17

1
同样的逻辑甚至适用于更复杂的随机化方案,甚至如Random.org的大气噪声方案。+1
jpmc26

38

纯函数是一种函数,其中返回值仅由其输入值确定,而没有明显的副作用

通过使用Math.random,您将通过输入值以外的方式确定其值。这不是一个纯粹的功能。

资源


25

不,它不是一个纯粹的功能,因为它的输出并不取决于上(的Math.random()能够输出的任何值)提供的输入,而纯函数应该总是输出相同的输入相同的值。

如果函数是纯函数,则可以安全地优化具有相同输入的多个调用,而只需重用先前调用的结果即可。

PS至少对我和其他许多人来说,redux使术语“ 纯函数”流行起来。直接来自redux文档

在减速器中永远不应该做的事情:

  • 改变其论点;

  • 执行副作用,例如API调用和路由转换;

  • 调用非纯函数,例如Date.now()或Math.random()。


3
尽管其他人提供了很好的答案,但是当我想到redux doc以及其中特别提到的Math.random()时,我无法抗拒自己:)
Shubhnik Singh

20

从数学的角度来看,您的签名不是

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的状态也可能已经改变!


7
更糟糕的是,环境也发生了变化,所以test: <environment, number, number> -> <environment, number>应该是
Bergi

1
我不确定OO示例是否非常相似。a.F(b, c)可以被视为语法糖,因为它F(a, b, c)具有一种特殊的规则,可以F根据类型来重载的重载定义a(这实际上是Python表示的方式)。但是a在两种表示法中仍然是明确的,而源代码中从未提及非纯函数中的环境。
IMSoP '17


10

除了可以正确指出此函数不确定性的其他答案外,它还具有副作用:它将导致将来的调用math.random()返回不同的答案。不具有该属性的随机数生成器通常会执行某种I / O,例如从OS提供的随机设备中读取。两者都是纯粹的功能。


7

不,不是。您根本无法弄清楚结果,因此无法测试这段代码。为了使该代码可测试,您需要提取生成随机数的组件:

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);

1
▲您对可测试性的想法。稍加注意,您也可以在接受的同时进行可重复的测试util.Random,您可以在测试运行开始时设定种子以重复旧的行为或进行新的(但可重复的)运行。如果是多线程,则可以在主线程中执行此操作,并将其用作Random可重复的本地线程的种子Random。但是,据我了解,test(int,int,Random)由于它会更改的状态,因此并不被认为是纯粹的Random
PJTraill '17

2

您可以使用以下方法吗?

return ("" + test(0,1)) + test(0,1);

相当于

var temp = test(0, 1);
return ("" + temp) + temp;

您会看到,pure的定义是一个函数,除了输入以外,其输出不会改变。如果我们说JavaScript有一种方法可以标记纯函数并加以利用,那么优化器将被允许将第一个表达式重写为第二个表达式。

我对此有实践经验。SQL服务器允许的getdate(),并newid()在“纯”的功能和优化器将重复数据删除随意调用。有时这会做一些愚蠢的事情。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.