Mixing Lists and Options in for comprehension

560 views
Skip to first unread message

Josh Hagins

unread,
Dec 17, 2014, 2:38:29 PM12/17/14
to scala...@googlegroups.com
Say I have the following classes defined:

case class Foo(bars: List[Bar])
case class Bar(bazs: Option[List[Baz]])
case class Baz(blarghs: List[Blargh])
class Blargh

Now say I want to obtain a List[Blargh] from a List[Foo].

scala> val foos = List(Foo(List(Bar(Some(List(Baz(List(new Blargh))))))))
foos: List[Foo] = List(Foo(List(Bar(Some(List(Baz(List(Blargh@14bcb102))))))))

scala> for {
     |   foo    <- foos
     |   bar    <- foo.bars
     |   bazs   <- bar.bazs
     |   baz    <- bazs
     |   blargh <- baz.blarghs
     | } yield blargh
<console>:32: error: type mismatch;
 found   : List[Blargh]
 required: Option[?]
                baz <- bazs
                    ^

What's going on here? Why can't I traverse over Options as well as Lists in a for comprehension?

A workaround is to just convert the Option[List[Bar]] to List[List[Bar]], but I feel like this shouldn't be necessary:

scala> for {
     |   foo    <- foos
     |   bar    <- foo.bars
     |   bazs   <- bar.bazs.toList
     |   baz    <- bazs
     |   blargh <- baz.blarghs
     | } yield blargh
res21: List[Blargh] = List(Blargh@14bcb102)

Josh Hagins

unread,
Dec 17, 2014, 2:40:30 PM12/17/14
to scala...@googlegroups.com
...convert the Option[List[Bar]] to List[List[Bar]]...

Should be Option[List[Baz]] to List[List[Baz]], sorry. 

Stefan Höck

unread,
Dec 18, 2014, 12:22:49 AM12/18/14
to scala...@googlegroups.com
> scala> val foos = List(Foo(List(Bar(Some(List(Baz(List(new Blargh))))))))
> foos: List[Foo] = List(Foo(List(Bar(Some(List(Baz(List(Blargh@14bcb102))))))))
>
>
> scala> for {
> | foo <- foos
> | bar <- foo.bars
> | bazs <- bar.bazs
> | baz <- bazs
> | blargh <- baz.blarghs
> | } yield blargh
> <console>:32: error: type mismatch;
> found : List[Blargh]
> required: Option[?]
> baz <- bazs

I managed to extract a reduced example:

This compiles:

def x : List[Option[Int]] = ???

for { y <- x; z <- y } yield z


This does not:

def x : Option[List[Int]] = ???

for { y <- x; z <- y } yield z


I *think* the reason is, that you can always transform an Option into a
List but not vice versa. Lets look at your larger code example and
desugare it:

foos.flatMap { foo =>
foo.bars.flatMap { bar =>
bar.bazs.flatMap { bazs =>
bazs.flatMap { baz =>
baz.blarghs
}
}
}
}

From this you see that the types are determined inside out.
The crucial part is this:

bazs.flatMap { baz =>
baz.blarghs
}

bazs has type Option[List[Blargh]] and from the minimal example above,
this cannot possibly compile, as you cannot convert a List to an Option.

IMO people should not mix container types in for comprehensions. It
reduces type safety and can lead to unexpected behavior especially after
several layers of nesting and especially if types like Set or Map are
involved, which sometimes silently remove some of your duplicates.
Therefore, manually transform your Option to a List and you'll be fine.

Som Snytt

unread,
Dec 18, 2014, 1:26:32 AM12/18/14
to scala-user
I think if the old One-Question FAQ had more than one question, this would be it.

http://stackoverflow.com/questions/4719592/type-mismatch-on-scala-for-comprehension

Also:

scala> val xs = (1 to 10).toList
xs: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

scala> val y = Some(100)
y: Some[Int] = Some(100)

scala> for (i <- xs ; j <- y) yield i * j
res0: List[Int] = List(100, 200, 300, 400, 500, 600, 700, 800, 900, 1000)

scala> for (i <- xs ; j <- y ; k <- xs) yield i * j * k
<console>:10: error: type mismatch;
 found   : List[Int]
 required: Option[?]
              for (i <- xs ; j <- y ; k <- xs) yield i * j * k
                                        ^

scala> :se -Xprint:typer

scala> for (i <- xs ; j <- y ; k <- xs) yield i * j * k
<console>:10: error: type mismatch;
 found   : List[Int]
 required: Option[?]
              for (i <- xs ; j <- y ; k <- xs) yield i * j * k
                                        ^
[[syntax trees at end of                     typer]] // <console>
package $line7 {
[snip]
        private[this] val res2: List[Nothing] = $line3.$read.$iw.$iw.xs.flatMap[Nothing, List[Nothing]](((i: Int) => scala.this.Option.option2Iterable[Nothing]($line4.$read.$iw.$iw.y.flatMap[Nothing](((j: Int) => $line3.$read.$iw.$iw.xs.map[Int, List[Int]](((k: Int) => i.*(j).*(k)))(immutable.this.List.canBuildFrom[Int]))))))(immutable.this.List.canBuildFrom[Nothing]);







--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Reply all
Reply to author
Forward
0 new messages