Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Random as instance of scalaz.Monad

This is a follow-up to my previous question. I wrote a monad (for an exercise) that is actually a function generating random values. However it is not defined as an instance of type class scalaz.Monad.

Now I looked at Rng library and noticed that it defined Rng as 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)
   }

So I wonder how exactly users benefit from that. How can we use the fact that Rng is an instance of type class scalaz.Monad ? Can you give any examples ?

like image 564
Michael Avatar asked Nov 23 '14 15:11

Michael


3 Answers

Here's a simple example. Suppose I want to pick a random size for a range, and then pick a random index inside that range, and then return both the range and the index. The second computation of a random value clearly depends on the first—I need to know the size of the range in order to pick a value in the range.

This kind of thing is specifically what monadic binding is for—it allows you to write the following:

val rangeAndIndex: Rng[(Range, Int)] = for {
  max <- Rng.positiveint
  index <- Rng.chooseint(0, max)
} yield (0 to max, index)

This wouldn't be possible if we didn't have a Monad instance for Rng.

like image 175
Travis Brown Avatar answered Oct 21 '22 06:10

Travis Brown


One of the benefit is that you will get a lot of useful methods defined in MonadOps.

For example, Rng.double.iterateUntil(_ < 0.1) will produce only the values that are less than 0.1 (while the values greater than 0.1 will be skipped).

iterateUntil can be used for generation of distribution samples using a rejection method. E.g. this is the code that creates a beta distribution sample generator:

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
}
like image 31
ZhekaKozlov Avatar answered Oct 21 '22 06:10

ZhekaKozlov


Like any interface, declaring an instance of Monad[Rng] does two things: it provides an implementation of the Monad methods under standard names, and it expresses an implicit contract that those method implementations conform to certain laws (in this case, the monad laws).

@Travis gave an example of one thing that's implemented with these interfaces, the Scalaz implementation of map and flatMap. You're right that you could implement these directly; they're "inherited" in Monad (actually a little more complex than that).

For an example of a method that you definitely have to implement some Scalaz interface for, how about sequence? This is a method that turns a List (or more generally a Traversable) of contexts into a single context for a List, e.g.:

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

But this actually only uses Applicative[Rng] (which is a superclass), not the full power of Monad. I can't actually think of anything that uses Monad directly (there are a few methods on MonadOps, e.g. untilM, but I've never used any of them in anger), but you might want a Bind for a "wrapper" case where you have an "inner" Monad "inside" your Rng things, in which case MonadTrans is useful:

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

To be totally honest though, Applicative is probably good enough for most Monad use cases, at least the simpler ones.

Of course all of these methods are things you could implement yourself, but like any library the whole point of Scalaz is that they're already implemented, and under standard names, making it easier for other people to understand your code.

like image 32
lmm Avatar answered Oct 21 '22 06:10

lmm