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 ?
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
.
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 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With