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

Kiw*_*ela 110 javascript function pure-function

以下是纯函数吗?

function test(min,max) {
   return  Math.random() * (max - min) + min;
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

Chr*_*ler 183

不,这不对.给定相同的输入,此函数将返回不同的值.然后你就无法构建一个映射输入和输出的"表".

来自Wikipedia的Pure函数文章:

在给定相同参数值的情况下,该函数始终评估相同的结果值.函数结果值不能依赖于程序执行过程中或程序执行不同时可能发生的任何隐藏信息或状态,也不依赖于I/O设备的任何外部输入

另外,另一件事是纯函数可以替换为表,该表表示来自输入和输出的映射,如此线程中所述.

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

function test(random, min, max) {
   return random * (max - min) + min;
}
Run Code Online (Sandbox Code Playgroud)

然后以这种方式调用它(例如,使用2和5作为最小值和最大值):

test( Math.random(), 2, 5)
Run Code Online (Sandbox Code Playgroud)

  • @cᴏʟᴅsᴘᴇᴇᴅ即便如此,它仍会产生副作用(改变未来的`Math.random`输出); 因为它是纯粹的,你必须以某种方式保存当前的RNG状态,重新设置它,调用`Math.random`,并将其恢复到以前的状态. (16认同)
  • @ LegionMammal978 ......并原子地这样做. (14认同)
  • 如果你在调用`Math.random`之前每次在函数内重新种子随机生成器怎么办? (2认同)
  • @cᴏʟᴅsᴘᴇᴇᴅ所有计算出的RNG都是基于伪随机性.必须在下面运行一些东西,使它看起来是随机的,你不能解释它,使它不纯净.此外,对您的问题可能更重要,您不能播种Math.random (2认同)
  • @cᴏʟᴅsᴘᴇᴇᴅ有些方法可以让RNG使用纯函数进行操作,但它涉及将RNG状态传递给函数并使函数返回替换RNG状态,这就是Haskell(一种强制执行功能纯度的函数式编程语言)的实现方式它. (2认同)

sen*_*rle 50

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

这里的许多其他答案都指出存在Math.random()意味着这个功能并不纯粹.但我认为值得说明为什么 Math.random() taints功能使用它.

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

在JavaScript中,涉及的过程依赖于实现,与许多其他语言不同,JavaScript 无法选择种子:

该实现选择初始种子到随机数生成算法; 它不能被用户选择或重置.

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

如果你想让它成为一个纯函数,你可以使用这里描述的替代随机数生成器之一.叫那个发电机seedable_random.它需要一个参数(种子)并返回一个"随机"数字.当然,这个数字根本不是随机的; 它由种子决定.这就是为什么这是一个纯粹的功能.seedable_random在根据输入预测输出很困难的意义上,输出仅是"随机的".

此函数的纯版本需要采用三个参数:

function test(min, max, seed) {
   return  seedable_random(seed) * (max - min) + min;
}
Run Code Online (Sandbox Code Playgroud)

对于任何给定的三个(min, max, seed)参数,这将始终返回相同的结果.

请注意,如果您想要的输出seedable_random真正随机的,你需要找到一种方法,随机种子!无论你使用什么策略都不可避免地是非纯粹的,因为它需要你从你职能之外的来源收集信息.正如mtraceurjpmc26提醒我的,这包括所有物理方法:硬件随机数发生器,带镜头盖的网络摄像头,大气噪声收集器 - 甚至熔岩灯.所有这些都涉及使用计算并存储在函数外部的数据.

  • Math.random()不仅读取它的"种子",而且还修改它,以便下一个调用将返回不同的东西.取决于和修改,静态对于纯函数肯定是坏的. (8认同)
  • @NateEldredge,非常好!虽然简单地阅读依赖于实现的值足以打破纯度.例如,有没有注意到Python 3哈希在进程之间是如何不稳定的? (2认同)
  • 如果`Math.random`没有使用PRNG而是使用硬件RNG实现,这个答案将如何改变?硬件RNG在正常意义上并不真正具有状态,但它确实产生随机值(因此无论输入如何,功能输出仍然不同),对吧? (2认同)

TKo*_*KoL 38

纯函数是一种函数,其中返回值仅由其输入值确定,没有可观察到的副作用

通过使用Math.random,您可以通过输入值以外的其他值来确定其值.这不是一个纯粹的功能.

资源


Shu*_*ngh 25

没有,它不是纯功能,因为它的输出不依赖上(的Math.random()可以输出任何值)提供的输入,而纯函数应该总是输出相同的输入相同的值.

如果函数是纯函数,则使用相同的输入优化掉多个调用是安全的,只需重用早期调用的结果即可.

对我来说至少PS和其他许多人,redux使术语纯粹功能受欢迎.直接来自redux文档:

你应该在减速机内做的事情:

  • 改变其论点;

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

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

  • 虽然其他人提供了很好的答案,但是当我想到redux doc并且Math.random()特别提到它们时,我无法抗拒自己:) (3认同)

Ada*_*ski 20

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

test: <number, number> -> <number>
Run Code Online (Sandbox Code Playgroud)

test: <environment, number, number> -> <environment, number>
Run Code Online (Sandbox Code Playgroud)

其中environment能够提供的结果Math.random().实际上,生成随机值会将环境变为副作用,因此您还将返回一个新环境,该环境不等于第一个环境!

换句话说,如果您需要任何不是来自初始参数(<number, number>部件)的输入,那么您需要提供执行环境(在此示例中为其提供状态Math).这同样适用于其他答案提到的其他事项,如I/O等.


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

SomeClass something
T result = something.foo(x, y)
Run Code Online (Sandbox Code Playgroud)

实际上我们正在使用

foo: <something: SomeClass, x: Object, y: Object> -> <SomeClass, T>
Run Code Online (Sandbox Code Playgroud)

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

  • 更糟糕的是,环境也发生了变异,所以`test:<environment,number,number> - > <environment,number>`它应该是 (7认同)

Ris*_*hra 11

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

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


Dav*_*lor 9

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


Héc*_*tor 7

不,不是.你根本无法弄清楚结果,所以这段代码无法测试.要使该代码可测试,您需要提取生成随机数的组件:

function test(min, max, generator) {
  return  generator() * (max - min) + min;
}
Run Code Online (Sandbox Code Playgroud)

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

const result = test(1, 2, () => 3);
result == 4 //always true
Run Code Online (Sandbox Code Playgroud)

在您的"生产"代码中:

const result = test(1, 2, Math.random);
Run Code Online (Sandbox Code Playgroud)