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

Is a function that calls Math.random() pure?

下面是纯函数吗?

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

我的理解是纯函数遵循这些条件:

  1. 它return是根据参数计算的值
  2. 除了计算 return 值
  3. 外,它不做任何工作

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

不,不是。给定相同的输入,此函数将 return 不同的值。然后你不能建立一个 'table' 来映射输入和输出。

来自 Pure function 的维基百科文章:

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

此外,另一件事是纯函数可以用 table 代替,它表示输入和输出的映射,如 this thread 中所述。

如果你想重写这个函数,把它变成一个纯函数,你也应该把随机值作为参数传递

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

然后这样称呼它(例如,最小值和最大值为 2 和 5):

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

纯函数是这样一种函数,其中 return 值仅由其输入值决定,没有可观察到的副作用

通过使用 Math.random,您可以通过输入值以外的其他方式确定其值。这不是一个纯函数。

source

纯函数总是return相同输入的相同值。 纯函数是可预测的并且是引用透明的,这意味着我们可以用输出替换函数调用 returned 并且它不会改变程序的工作。

https://github.com/MostlyAdequate/mostly-adequate-guide/blob/master/ch3.md

不,不是。你根本想不出结果,所以这段代码没法测试。为了使该代码可测试,您需要提取生成随机数的组件:

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

现在,您可以模拟生成器并正确测试您的代码:

const result = test(1, 2, () => 3);
result == 4 //always true

在您的 "production" 代码中:

const result = test(1, 2, Math.random);

不,它不是纯函数,因为它的输出不依赖于所提供的输入(Math.random() 可以输出任何值),而纯函数应该始终为相同的输入输出相同的值。

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

P.S 至少对我和其他许多人来说,redux 使术语 纯函数 流行起来。 Straight from the redux docs:

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().

您问题的简单答案是 Math.random() 违反了规则 #2。

这里很多其他回答都指出Math.random()的存在说明这个函数不纯。但我认为值得一提的是 为什么 Math.random() 污染了使用它的函数。

与所有伪随机数生成器一样,Math.random() 以 "seed" 值开头。然后它使用该值作为一系列低级位操作或其他导致不可预测(但不是真正的随机)输出的操作的起点。

在 JavaScript 中,所涉及的过程是依赖于实现的,与许多其他语言不同,JavaScript 提供 no way to select the seed:

The implementation selects the initial seed to the random number generation algorithm; it cannot be chosen or reset by the user.

这就是此函数不纯粹的原因:JavaScript 本质上是使用您无法控制的隐式函数参数。它从其他地方计算和存储的数据中读取该参数,因此违反了您定义中的规则 #2。

如果您想使其成为一个纯函数,您可以使用 here 中描述的替代随机数生成器之一。调用该生成器 seedable_random。它需要一个参数(种子)和 return 一个 "random" 数字。当然,这个数字根本不是随机的。它由种子唯一确定。这就是为什么这是一个纯函数。 seedable_random 的输出只有 "random",因为根据输入预测输出很困难。

这个函数的纯版本需要三个个参数:

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

对于任何给定的 (min, max, seed) 参数三元组,这将始终 return 相同的结果。

请注意,如果您希望 seedable_random 的输出 真正 随机,您需要找到一种随机化种子的方法!无论您使用何种策略都不可避免地是非纯粹的,因为它需要您从职能之外的来源收集信息。如 and remind me, this includes all physical approaches: hardware random number generators, webcams with lens caps, atmospheric noise collectors -- even lava lamps。所有这些都涉及使用在函数外部计算和存储的数据。

除了正确指出此函数如何不确定的其他答案外,它还有一个副作用:它会导致将来对 math.random() 到 return 的不同调用回答。没有 属性 的随机数生成器通常会执行某种 I/O,例如从 OS 提供的随机设备中读取。两者都被禁止用于纯函数。

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

test: <number, number> -> <number>

但是

test: <environment, number, number> -> <environment, number>

其中 environment 能够提供 Math.random() 的结果。 实际上生成随机值会改变环境作为副作用,所以你也 return 一个新的环境,它不等于第一个!

换句话说,如果您需要任何类型的输入,而不是来自初始参数(<number, number> 部分),则需要为您提供执行环境(在本例中为Math)。这同样适用于其他答案提到的其他事情,例如 I/O 等。


作为类比,您还可以注意到这是面向对象编程的表示方式 - 如果我们说,例如

SomeClass something
T result = something.foo(x, y)

那么实际上我们正在使用

foo: <something: SomeClass, x: Object, y: Object> -> <SomeClass, T>

调用其方法的对象是环境的一部分。为什么结果的 SomeClass 部分?因为 something 的状态也可能发生变化!

您可以接受以下内容吗:

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

相当于

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

?

你看,pure 的定义是一个函数,它的输出不随输入以外的任何东西而变化。如果我们说 JavaScript 有办法标记一个函数 pure 并利用它,优化器将被允许将第一个表达式重写为第二个。

我有这方面的实践经验。 SQL 服务器在 "pure" 函数中允许 getdate()newid(),优化器将随意删除重复调用。有时这会做一些愚蠢的事情。