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

下面是一个纯函数吗?

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

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

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

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

17201 次浏览

不,不是的。给定相同的输入,该函数将返回不同的值。然后,您就不能构建一个映射输入和输出的“表”。

来自维基百科 纯粹的功能的文章:

该函数总是计算给定相同的结果值 参数值。函数结果值不能依赖于任何 程序执行时可能改变的隐藏信息或状态 在程序的不同执行之间,它也不能 依赖于任何来自 I/O 设备的外部输入

另外,一个纯函数可以替换为一个表,表示来自输入和输出的映射,如 这根线中所解释的。

如果要重写这个函数并将其改为纯函数,也应该将随机值作为参数传递

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

然后这样称呼它(例如,将2和5作为 min 和 max) :

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

A pure function is a function where the return value is only determined by its input values, without observable side effects

通过使用 Math.Random,您可以通过输入值以外的其他东西来确定它的值,它不是一个纯函数。

来源

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

Https://github.com/mostlyadequate/mostly-adequate-guide/blob/master/ch3.md

不,不是的。您根本无法计算出结果,因此无法对这段代码进行测试。为了使代码可测试,您需要提取生成随机数的组件:

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

Now, you can mock the generator and test your code properly:

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

在你的“生产”代码中:

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

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

如果一个函数是纯函数,那么使用相同的输入优化掉多个调用并重用早期调用的结果是安全的。

至少对我和其他许多人来说,redux 使 pure function这个术语变得流行起来:

你在减速器里不应该做的事情:

  • 改变它的论点;

  • 执行 API 调用和路由转换等副作用;

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

你问题的简单答案是 Math.random()违反了第二条规则。

这里的许多其他答案指出,Math.random()的存在意味着这个函数不纯。但我认为值得一提的是,why Math.random()会破坏使用它的函数。

Like all pseudorandom number generators, Math.random() starts with a "seed" value. It then uses that value as the starting point for a chain of low-level bit manipulations or other operations that result in an unpredictable (but not really 随机的) output.

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

该实现向随机数生成算法选择初始种子,用户无法选择或重置该种子。

这就是为什么这个函数不是纯粹的: JavaScript 实际上使用了一个你无法控制的隐函数参数。它从其他地方计算和存储的数据中读取该参数,因此违反了定义中的规则 # 2。

If you wanted to make this a pure function, you could use one of the alternative random number generators described here. Call that generator seedable_random. It takes one parameter (the seed) and returns a "random" number. Of course, this number isn't really random at all; it is uniquely determined by the seed. That's why this is a pure function. The output of seedable_random is only "random" in the sense that predicting the output based on the input is difficult.

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

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

For any given triple of (min, max, seed) parameters, this will always return the same result.

注意,如果您希望 seedable_random的输出是 真的随机的,那么您需要找到一种方法来随机化种子!无论您使用什么策略,都不可避免地是非纯粹的,因为它需要您从您的功能之外的来源收集信息。正如 mtraceurJpmc26提醒我的那样,这包括所有的物理方法: 硬件随机数发生器硬件随机数发生器带镜头盖的网络摄像机atmospheric noise collectors——甚至是 熔岩灯。所有这些都涉及到使用函数外部计算和存储的数据。

In addition to the other answers that correctly point out how this function is non-deterministic, it also has a side-effect: it will cause future calls to math.random() to return a different answer. And a random-number generator that doesn’t have that property will generally perform some kind of I/O, such as to read from a random device provided by the OS. Either is verboten for a pure function.

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

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

但是

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

where the environment is capable of providing results of 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的状态也可能发生了变化!

你能接受下面这些吗:

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

等同于

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

?

可以看到,纯函数的定义是一个函数,其输出除了其输入以外不会发生任何变化。如果我们说 JavaScript 有一种方法可以纯粹地标记函数并利用这一点,那么优化器就可以将第一个表达式重写为第二个表达式。

我有这方面的实践经验。SQL 服务器允许在“纯”函数中使用 getdate()newid(),并且优化器可以随意忽略调用。有时候这会做些傻事。