Polymerase
Polymerase

Reputation: 6781

Why does For Comprehension generator suppress Option type?

In the code below, version 1 gives the correct result. I make a small variation in V2. The None value had disappeared which is Ok as that is how For Expression works. But what is the reason the yield output in V2 no longer respects the data type returned by myList.lift() which is an Option (as in V1)?

val myList = List(12, 34, "ABC")

Version 1

for { i <- (0 to 3).toList } yield myList.lift(i)
// res1: List[Option[Any]] = List(Some(12), Some(34), Some(ABC), None)

Version 2

for { 
  i <- (0 to 3).toList 
  x <- myList.lift(i)
} yield x
// res2: List[Any] = List(12, 34, ABC)

Upvotes: 2

Views: 337

Answers (2)

Gordon Gustafson
Gordon Gustafson

Reputation: 41209

Desugaring the first case:

// desugar for comprehension:
(0 to 3).toList.map(
  i => myList.lift(i))

Desugaring the second case:

// desugar for comprehension:
(0 to 3).toList.flatMap(
  i => myList.lift(i).map(
    x => x))

// remove .map(x => x):
(0 to 3).toList.flatMap(
  i => myList.lift(i))

// desugar flatMap:
(0 to 3).toList.map(
  i => myList.lift(i)).flatten

The second case simplifies to the first case with a .flatten at the end, which explains the difference in the results: res2 = res1.flatten.

What's actually going on?

Scala can treat Option as a sequence:

Some(foo) --> Seq(foo)
None      --> Seq()

The .flatten is just flattening the sequence of sequences.

If you're curious about the types:

  • scala.collection.Seq.flatten requires that the 'inner' type have an implicit conversion to GenTraversableOnce[T]
  • there's a global implicit conversion from Option[T] to Iterable[T]
  • Iterable[T] <: GenTraversableOnce[T]

What does <- mean then?

The <- in x <- myList.lift(i) doesn"t just assign a variable to a value, it "gets a value out of" myList.lift(i). When you "get a value out of" an Option[T], you get foo for Some(foo) and nothing for None. "Getting nothing" means the yield doesn"t run at all for a None, so nothing shows up in the result for the "iteration" when i = 3.

If you're curious about this "get a value out of" concept that is defined for Seq, Option, and many other types in Scala, it is defined for any Monad.

Upvotes: 7

wmmeyer
wmmeyer

Reputation: 466

If you de-sugar the for comprehensions

Version 1

List(0, 1, 2, 3).map({ i => 
  myList.lift(i) 
})

Version 2

List(0, 1, 2, 3).flatMap({ i => 
  myList.lift(i).map({ x => x })
})

what is the reason the yield output in V2 no longer respects the data type returned by myList.lift()

The yield does nothing to the output of List.lift:

  • myList.lift(i) returns Option[Any]
  • myList.lift(i).map({ x => x }) returns Option[Any]

It's the flatMap that flattens the Option[Any] to Any (by discarding None and un-wrapping Some(a: Any) => a)

Upvotes: 3

Related Questions