1

I am scratching my head on an example I've seen in breeze's documentation about distributions.

After creating a Rand instance, they show that you can do the following:

import breeze.stats.distributions._

val pois = new Poisson(3.0);
val doublePoi: Rand[Double] = for(x <- pois) yield x.toDouble

Now, this is very cool, I can get a Rand object that I can get Double instead of Int when I call the samples method. Another example might be:

val abc = ('a' to 'z').map(_.toString).toArray
val letterDist: Rand[String] = for(x <- pois) yield {
val i = if (x > 26) x % 26 else x
abc(i)
}
val lettersSamp = letterDist.samples.take(20)
println(letterSamp)

The question is, what is going on here? Rand[T] is not a collection, and all the for/yield examples I've seen so far work on collections. The scala docs don't mention much, the only thing I found is translating for-comprehensions in here. What is the underlying rule here? How else can this be used (doesn't have to be a breeze related answer)

  • 2
    LIke mentioned in the link, a for comprehension is basically syntactic sugar for a combinations of `flatMap`s and one `map`. You can write a for comprehension because [`Poisson`](http://www.scalanlp.org/api/breeze/#breeze.stats.distributions.Poisson) implements both `flatMap` and `map`. In your case the for comprehension is analogous to `pois.map(_.toDouble)`. – Peter Neyens Jun 12 '16 at 08:52
  • Also, a random distribution *is* a collection. It's a rather strange collection, that **maybe** contains **each** element instead of **definitely** containing **specific** elements, but you can interpret it as a collection. (A Bloom filter is an even weirder one, if you think about it.) – Jörg W Mittag Jun 12 '16 at 10:00

2 Answers2

1

Scala has rules for translating for and for-yield expressions to the equivalent flatMap and map calls, optionally also applying filters using withFilter and such. The actual specification for how you translate each for comprehension expression into the equivalent method calls can be found in this section of the Scala Specification.

If we take your example and compile it we'll see the underlying transformation happen to the for-yield expression. This is done using scalac -Xprint:typer command to print out the type trees:

val letterDist: breeze.stats.distributions.Rand[String] = 
        pois.map[String](((x: Int) => {
          val i: Int = if (x.>(26))
            x.%(26)
          else
            x;
          abc.apply(i)
}));

Here you can see that for-yield turns into a single map passing in an Int and applying the if-else inside the expression. This works because Rand[T] has a map method defined:

def map[E](f: T => E): Rand[E] = MappedRand(outer, f)
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
0

For comprehensions are just syntactic sugar for flatMap, map and withFilter. The main requirement for use in a for comprehension is that those methods are implemented. They are therefore not limited to collections E.g. some common non-collections used in for comprehensions are Option, Try and Future.

In your case, Poisson seems to inherit from a trait called Rand

https://github.com/scalanlp/breeze/blob/master/math/src/main/scala/breeze/stats/distributions/Rand.scala

This trait has map, flatmap, and withFilter defined.

Tip: If you use an IDE like IntelliJ - you can press alt+enter on your for comprehension, and chose convert to desugared expression and you will see how it expands.

Ulf Gjerdingen
  • 1,414
  • 3
  • 16
  • 20
Bruce Lowe
  • 6,063
  • 1
  • 36
  • 47