问题描述
最近,我开始研究使用Scala作为参考语言的函数式编程范例。 我想到了一个问题:如何生成没有副作用的随机数? 谷歌搜索,我找到了这个解决方案:
package fp.crazy-bankers.utils
object Rng {
def next(seed : Int) : (Int,Int) = {
val newSeed = (seed * 0x5DEECE66DL + 0xBL) & 0x FFFFFFFFFFFFL
val number = (newSeed >>> 16).toInt
(newSeed,number)
}
}
基本上,这里管理状态(种子),并在每次调用next方法时显式地传递它。 该实现对我一直有效,直到我从同一个地方调用它为止,因为以这种方式很容易保持状态并在每次调用时将其传递。
但是,如果我需要在其他地方使用随机数怎么办? 例如,如果我从10个不同的角色调用next方法, 在这种情况下,每个参与者只能传递其状态的本地副本。
基本上,这种方式在所有参与者之间都没有关于国家的全球知识,因此 风险是不同的参与者获得相同的随机数。 如何解决这个问题?
我是否找到了某种方法来全局管理状态或尝试使用完全不同的模式?
解决方法
您可以引入一个单独的演员,该演员将用下一个随机数答复。
例如,演员会将GetNextRandomNumber
发送给该演员,并会回复NextRandomNumber(number)
。
此参与者将自行管理其状态(seed
)。
实际上向该演员发送消息是一种副作用。
通常生成随机数是一个副作用,因为没有副作用的函数不能在同一输入上产生不同的输出。
函数式编程的思想不是避免副作用,而是要控制它们。例如IO
,State
等
您可以使用Ref
构造在不同位置之间共享可变状态。 Scala中有几种实现方法:
ZIO参考:https://zio.dev/docs/datatypes/datatypes_ref cats-effect参考:https://typelevel.org/cats-effect/concurrency/ref.html
但是,这需要您将Ref显式传递到将要使用它的每个位置。这是一个功能,而不是错误,因为它使识别使用某种可变状态的所有位置变得更加容易-无法拥有全局可变状态。
但是既然您提到了演员,我怀疑您是否真的在做纯粹的FP。基本上至少没有办法用Akka做到这一点,因为即使在演员之间发送消息也是副作用。
,如果要“使用纯功能设计风格”,则必须将种子传递给每个需要“随机”数字的功能。别无选择。所有其他选项都需要副作用,否则将失去参照透明性。
因此,如果您在多个函数中使用随机数,则必须为每个函数提供不同的种子。
种子只是表示无限数量随机数的一种紧凑方式,因此一种替代方法是在函数外部生成随机数,然后将值传递给函数而不是种子。
,您使用支持拆分的另一种PRNG类型。然后,您可以根据需要在主要角色上拆分生成器多次,然后将一个子生成器发送给其他每个角色。
例如,使用名为JAX does this的生成器threefry。