随机数作为scalaz.Monad的实例

8
这是对我之前的问题进行跟进的内容。我编写了一个单子(作为练习),它实际上是生成随机值的函数。然而,它没有被定义为类型类scalaz.Monad的一个实例。
现在我看了Rng ,并注意到它将Rng定义为scalaz.Monad
implicit val RngMonad: Monad[Rng] =
   new Monad[Rng] {
      def bind[A, B](a: Rng[A])(f: A => Rng[B]) = a flatMap f
      def point[A](a: => A) = insert(a)
   }

我想知道用户究竟从中受益了什么。我们如何利用 Rng 是类型类 scalaz.Monad 的一个实例的事实?你能举个例子吗?

3个回答

7
这是一个简单的例子。假设我想在范围内选择一个随机大小,然后选择该范围内的随机索引,然后返回范围和索引。第二次计算随机值显然取决于第一次——我需要知道范围的大小才能在范围内选择一个值。
这种情况正是单子绑定的用途,它允许你编写以下内容:
val rangeAndIndex: Rng[(Range, Int)] = for {
  max <- Rng.positiveint
  index <- Rng.chooseint(0, max)
} yield (0 to max, index)

如果我们没有为 Rng 实现 Monad 实例,那么这将不可能实现。

1
谢谢。我想我明白了,但我在问其他事情。即使Rng没有定义为scalaz.Monad的实例,这个例子也可以工作。只需将flatMapmap添加到Rng中(如果我们添加一个return函数并检查法律,则会使其成为单子),是吗?我特别询问那个隐式的val RngMonad:Monad [Rng] = ...东西。 - Michael
没错,语法糖总是能够工作的,但是当你在 Scalaz 的上下文中工作时,提供一个 scalaz.Monad 实例就是“将某物变成单子”的所涉及的内容。通过 MonadOps 获得其他一些方法似乎有点偶然性。 - Travis Brown
我明白了。所以我可能应该重新表述问题:在 Scalaz 的背景下,将 Rng 设为一个单子有什么好处?目前我只看到一个好处,那就是与 scalaz.Monad 一起工作时可以使用 Scalaz 的方法。也许我漏掉了什么…… - Michael
泛化和重用是其中的两个原因。许多有用的操作(包括MonadOps中的操作)可以在高层次上定义并在许多上下文中使用。为什么要为Rng编写一个特殊的whileM,而不是编写一个适用于任何单子的函数呢? - Travis Brown
泛化和重用是我正在寻找的。更准确地说,我正在寻找定义在scalaz.Monad中并可应用于“随机生成器”领域的通用操作。例如,可以应用iterateUntil来编写Beta样本生成器(如下所示)。我会考虑使用whileM的情况。 - Michael

3

其中一个好处是您将获得许多在MonadOps中定义的有用方法。

例如,Rng.double.iterateUntil(_ < 0.1)将仅生成小于0.1的值(大于0.1的值将被跳过)。

iterateUntil可用于使用拒绝法生成分布样本。例如,以下是创建beta分布样本生成器的代码:

import com.nicta.rng.Rng
import java.lang.Math
import scalaz.syntax.monad._

object Main extends App {

  def beta(alpha: Double, beta: Double): Rng[Double] = {
    // Purely functional port of Numpy's beta generator: https://github.com/numpy/numpy/blob/31b94e85a99db998bd6156d2b800386973fef3e1/numpy/random/mtrand/distributions.c#L187
    if (alpha <= 1.0 && beta <= 1.0) {
      val rng: Rng[Double] = Rng.double

      val xy: Rng[(Double, Double)] = for {
        u <- rng
        v <- rng
      } yield (Math.pow(u, 1 / alpha), Math.pow(v, 1 / beta))

      xy.iterateUntil { case (x, y) => x + y <= 1.0 }.map { case (x, y) => x / (x + y) }
    } else ???
  }

  val rng: Rng[List[Double]] = beta(0.5, 0.5).fill(10)

  println(rng.run.unsafePerformIO) // Prints 10 samples of the beta distribution
}

谢谢。这正是我要找的样例。不幸的是,我不知道什么是“beta generator”。我更喜欢一个更简单的iterateUntil的示例。 - Michael
顺便问一下,我能否使用 iterateUntil 生成给定大小的随机值序列? - Michael
要生成一系列随机值,您可以使用Rng.fill - ZhekaKozlov
我更感兴趣的是实现部分。Rng.fill 是用 sequence 实现的。我想知道它是否也可以用 iterateUntil 实现。 - Michael
1
不,我不这么认为。但是你可以使用 rng.replicateM 来获得与 Rng.fill 相同的结果。 - ZhekaKozlov

2

像任何接口一样,声明 Monad[Rng] 的一个实例需要做两件事情:它提供了标准名称下的 Monad 方法的实现,并表达了这些方法实现符合特定法律(在这种情况下是单子定律)的隐式契约。

@Travis 给出了使用这些接口实现的一个示例,即 Scalaz 实现中的 mapflatMap。你是对的,你可以直接实现它们;它们在 Monad 中被“继承”(实际上比这更加复杂)。

至于必须为某个 Scalaz 接口实现的方法的示例,那么 sequence 如何?这是一种将上下文的 List(或更普遍地说,Traversable)转换为 List 的单个上下文的方法,例如:

val randomlyGeneratedNumbers: List[Rng[Int]] = ...
randomlyGeneratedNumbers.sequence: Rng[List[Int]]

但实际上这仅仅使用了Applicative[Rng](它是一个超类),而没有充分发挥Monad的全部威力。我实际上无法想到任何直接使用Monad的内容(有一些在MonadOps上的方法,比如untilM,但我从未愤怒地使用过它们中的任何一个),但您可能需要一个Bind作为一个“包装器”的情况,其中您拥有一个“内部”Monad“在”您的Rng事物中,此时MonadTrans非常有用:

val a: Rng[Reader[Config, Int]] = ...
def f: Int => Rng[Reader[Config, Float]] = ...
//would be a pain to manually implement something to combine a and f
val b: ReaderT[Rng, Config, Int] = ...
val g: Int => ReaderT[Rng, Config, Float] = ...
b >>= g

老实说,Applicative 对于大多数 Monad 使用场景来说已经足够好了,至少对于简单的情况是这样。

当然,所有这些方法你都可以自己实现,但就像任何库一样,Scalaz 的整个重点在于它们已经被实现了,并且使用标准名称,这使得其他人更容易理解你的代码。


谢谢。sequence 是我想到的例子。它使用 Applicative 是可以的。 - Michael

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接