2

I have difficulty understanding the mechanism using for-compression in Scala. For example, if I have

val x = for {
  i <- Option(1)
  j <- Option(2)
  k <- Option(3)
} yield (i,j,k)

x is x: Option[(Int, Int, Int)] = Some((1,2,3)). However, if at least one of component is None, for example,

val x = for {
  i <- Option(1)
  j <- Option(2)
  k <- None
} yield (i,j,k)

then x is x: Option[(Int, Int, Nothing)] = None, while I actually hoped to see something like : x: Option[(Int, Int, Nothing)] = Some((1,2,None)).

I have checked the FAQ from Scala official documentation where it is specifically stated that for-comprehension is a combination of flatmap and map. But I still have difficulty understanding that x is None.

I think I missed some important concepts on flatmap and map difference.

helloworld
  • 613
  • 8
  • 24
  • 1
    `for { a <- A; b <- B } yield b` is the same as `A.flatMap { a => B.map { b => b } }`. Also, `None.flatMap(f) === None` & `None.map(f) === None`. Thus, as you said, if at least one element is a **None**, then the result of the for will be **None** _(that is why it is said that None acts like a short circuit, because it will ignore everything after it)_. - Additionally, `Option[(Int, Int, Int)] = Some((None,2,3))` is wrong, since you said the first element of the tuple will be an `Int` and you passed it a **None** _(which has type `Option[Nothing]`)_ so it will not type check. Hope it helps :) – Luis Miguel Mejía Suárez May 14 '19 at 20:23

1 Answers1

6

The first for-comprehension "desugars" into:

val x = Option(1).flatMap(
  i => Option(2).flatMap(
    j => Option(3).map(
      k => (i, j, k)
    )
  )
)

As you can see - the first Option is flat-mapped using a function that takes its value (if it exists) and returns a 3-tuple (using similar operation on the 2nd Option). Regardless of what that function is - the entire expression is of the form:

val x = Option(1).flatMap(f)

Now, if we replace Option(1) with None (as you did in second expression) we'd clearly get None:

val x = None.flatMap(f) // None, for any f

The result that you expected (Some((None, 2, 3))) is not too useful - as it would have different types for different inputs: Would it be (Option[Int], Int, Int)? Or (Int, Int, Int)? Or (Option[Int], Option[Int], Option[Int])? Really the only common type for (None, 2, 3), (1, None, 3) and (1, 2, None) is the not-so-useful (Any, Any, Any).

Tzach Zohar
  • 37,442
  • 3
  • 79
  • 85
  • 1
    Interesting point re: the type usefulness, but I would tend to see it more a consideration of consistency across collections (with `Option` being treated as a collection). Consider the case of a `for-comprehension` on `List` like `for { i <- List(); j <- List(1); k <- List(2) } yield (i, j, k)`, which could be viewed as collecting tuples of (i, j, k) while traversing a 3-dimensional array in the conventional `for-loop`. If one of the dimensions is of size `0`, the size of the resultant `List` should be `0` (in this case `0 x 1 x 1`). – Leo C May 14 '19 at 22:26