【问题标题】:Why `Random.nextInt` is considered not ' composable, modular, easily parallelized'为什么“Random.nextInt”被认为不是“可组合的、模块化的、易于并行化的”
【发布时间】:2015-07-18 04:14:13
【问题描述】:

在《Scala 中的函数式编程》一书中,有一节介绍了如何以函数式方式编写随机数生成器。

举了一个关于scala.util.Random的例子:

即使我们对 scala.util.Random 内部发生的事情一无所知,我们也可以假设对象 rng 有一些在每次调用后都会更新的内部状态,否则我们每次都会得到相同的值我们称为 nextInt 或 nextDouble。因为状态更新是作为副作用执行的,所以这些方法在引用上不是透明的。正如我们所知,这意味着它们不像它们应有的那样可测试、可组合、模块化和易于并行化。

最后一句,它说不是:

  1. 可测试
  2. 可组合
  3. 模块化
  4. 易于并行化

在后面的内容中,解释了testable部分,但是其他3个方面怎么理解呢?

【问题讨论】:

    标签: scala random functional-programming


    【解决方案1】:

    1。可测试

    您无法轻松隔离其状态,尤其是在并行运行测试时。这使得在并行运行测试时无法以可预测的方式重现随机数序列。

    2。可组合

    您不能构建一个操作序列,首先使用已知值播种随机数,然后获取与该种子有关的序列(尤其是在并行运行时)。

    虽然这可能与“可测试”重叠,但出于安全原因,它也可能是必要的,因此您不会意外地将状态从一个会话泄漏到另一个会话。

    3。模块化

    没有办法交换不同的随机算法。所有模块都必须使用相同的算法。这与“可组合”重叠,因为您也不能将生成器提供为(可能是隐式的)参数,而不是种子。

    4。并行化

    所有线程只有一种状态。在高度并发的情况下,这可能是一个严重的瓶颈。

    一些示例代码

    请不要在生产中使用此代码。这很笨拙,使用scalazcats 您可以轻松构建更好的东西。通常,随机数生成器是一个共单子,它的状态被保存在单子中​​。

    首先让我们通过提供不同的伪随机生成器和不同的种子来使代码模块化

    // This is a commonly used pseudo-random generator
    // https://en.wikipedia.org/wiki/Linear_congruential_generator
    // This implementation is awful slow!
    def lcg(multiplier : BigInt, increment : BigInt, dividend : BigInt)(seed : BigInt) =
      (seed * multiplier + increment) mod dividend
    
    // Some commonly used pseudo random number generators
    def glibRand = lcg(multiplier = 1103515245,   increment = 12345,   dividend = 2^31) _
    def vb6Rand  = lcg(multiplier = 214013,       increment = 2531011, dividend = 2^31) _
    def javaRand = lcg(multiplier = 25214903917L, increment = 11,      dividend = 2L^48) _
    
    
    // This is really insecure, don't use it for anything serious!
    def seedFromTime() = BigInt(System.nanoTime())
    
    // I used a dice to determine this value, so it's random!
    def randomSeed = 5 
    

    那么让我们构建一个可以组合函数的实现:

    case class Random[T,U](val generator : T => T, val seed : T)(val currentValue : U){
     def apply[V](f : (T,U) => V) : Random[T,V] = {
       val nextRandom = generator(seed)
       Random(generator, nextRandom)(f(nextRandom, currentValue))
      }
    
      def unsafeGetValue = currentValue
    }
    

    我们使用此构建块生成随机数列表并对一些随机数求和的几个示例:

    val randomNumbers  = Stream.iterate(Random(glibRand, seedFromTime())(List.empty[BigInt])){r : Random[BigInt, List[BigInt]] =>
      r.apply{(rnd : BigInt, numbers : List[BigInt]) =>
        rnd :: numbers
      }
    }
    
    randomNumbers.take(10).last.unsafeGetValue
    
    val randomSum = Stream.iterate(Random(glibRand, seedFromTime())(BigInt(0))) { r: Random[BigInt, BigInt] =>
      r.apply(_ + _)
    }
    
    randomSum.take(100).last.unsafeGetValue
    

    像往常一样,当我们想用函数式语言组合某些东西时,我们会组合函数来达到不同的目标。如果我们提供一个单子实现,我们还可以将函数与副作用结合起来。

    在这种情况下(与往常一样),并行化和可测试性会自动进行。

    【讨论】:

    • 我没有这本书,但 scala.concurrent.forkjoin.ThreadLocalRandom 不是第 4 点的答案吗?
    • 谢谢,关于“可组合”,我现在有一个提前的问题:stackoverflow.com/questions/31444762/…
    • 关于“模块化”,您能否举一个代码示例来说明如何使其“模块化”?
    • 当我看到“模块化”这个词时,我认为它是“模块”,它是一个容器,它有一些公共方法来调用和隐藏内部状态。是“模块化”的意思吗?
    • @Freewind Modular 对我来说意味着“关注点分离”,在这种情况下,关注点可能是(这不是精确的科学并且总是固执己见):管理状态、生成随机数、播种。当我有空闲时间时,我会尝试为各个方面添加一些示例代码。
    猜你喜欢
    • 2015-10-05
    • 2021-08-18
    • 2014-02-22
    • 1970-01-01
    • 1970-01-01
    • 2011-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多