Random as instance of scalaz.Monad
Asked Answered
T

3

8

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 ?

Tragopan answered 23/11, 2014 at 15:27 Comment(0)
T
3

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
}
Trespass answered 24/11, 2014 at 9:9 Comment(5)
Thank you. This is the kind of examples I am looking for. Unfortunately, I don't what "beta generator" is. I would prefer a simpler example of iterateUntil.Tragopan
BTW, can I use iterateUntil to generate a sequence of random values of a given size ?Tragopan
To generate a sequence of random values use can use Rng.fillTrespass
I am more interested in the implementation. Rng.fill is implemented w/ sequence. I wonder if it can be implemented with iterateUntil as well.Tragopan
No, I don't think so. But you can use rng.replicateM to get the same result as Rng.fill.Trespass
D
7

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.

Dripping answered 23/11, 2014 at 16:21 Comment(5)
Thank you. I think I got it but I am asking about smth. else. This example works even if Rng is not defined an instance of scalaz.Monad technically. It's enough just to add flatMap and map to Rng (that makes it a monad if we add a return function and check the laws), isn't it ? I am asking specifically about that implicit val RngMonad: Monad[Rng] = ... thing.Tragopan
Right, the syntactic sugar would work anyway, but when you're working in the context of Scalaz, providing a scalaz.Monad instance is what's involved in "making something a monad". The fact that you get some other methods via MonadOps seems kind of incidental.Dripping
I see. So I probably should rephrase the question: what are the benefits of making Rng a monad in the context of Scalaz ? The only benefit I see now is Scalaz methods that work with/against scalaz.Monad. Maybe I am missing something ...Tragopan
Well, generalization and reuse are a couple of reasons. Lots of useful operations (including the ones in MonadOps) can be defined at a high level and used in many contexts. Why write e.g. a special whileM for Rng when you can write one that works for any monad?Dripping
Generalization and reuse are what I am looking for. More precisely, I am looking for such generic operations defined for scalaz.Monad that can be applied in the "random generators" domain. For example, iterateUntil can be applied to write a beta sample generator (as it is shown below). I will think about a use case for whileM.Tragopan
T
3

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
}
Trespass answered 24/11, 2014 at 9:9 Comment(5)
Thank you. This is the kind of examples I am looking for. Unfortunately, I don't what "beta generator" is. I would prefer a simpler example of iterateUntil.Tragopan
BTW, can I use iterateUntil to generate a sequence of random values of a given size ?Tragopan
To generate a sequence of random values use can use Rng.fillTrespass
I am more interested in the implementation. Rng.fill is implemented w/ sequence. I wonder if it can be implemented with iterateUntil as well.Tragopan
No, I don't think so. But you can use rng.replicateM to get the same result as Rng.fill.Trespass
L
2

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.

Lucchesi answered 24/11, 2014 at 10:52 Comment(1)
Thank you. The sequence is the example I am thinking of. It is Ok that it uses Applicative.Tragopan

© 2022 - 2024 — McMap. All rights reserved.