16
votes

Concerning the yield command in Scala and the following example:

val values = Set(1, 2, 3)
val results = for {v <- values} yield (v * 2)

  • Can anyone explain how Scala knows which type of collection to yield into? I know it is based on values, but how would I go about writing code that replicates yield?
  • Is there any way for me to change the type of the collection to yield into? In the example I want results to be of type List instead of Set.
  • Failing this, what is the best way to convert from one collection to another? I know about _:*, but as a Set is not a Seq this does not work. The best I could find thus far is val listResults = List() ++ results.

    Ps. I know the example does not following the recommended functional way (which would be to use map), but it is just an example.

  • 3
    A minor note, while I find map more convenient, I would not call it more or less functional than for, as it is exactly the same thing. for{v <- values} yield (v*2) is compiled as values.map{v => v * 2} (sort of macro expansion). It is definitely not a loop : the implementation of map in Set is responsible for looping if needed, not the for comprehension.Didier Dupont
    @didierd - good to know.Zecrates

    3 Answers

    23
    votes

    The for comprehensions are translated by compiler to map/flatMap/filter calls using this scheme.

    This excellent answer by Daniel answers your first question.

    To change the type of result collection, you can use collection.breakout (also explained in the post I linked above.)

    scala> val xs = Set(1, 2, 3)
    xs: scala.collection.immutable.Set[Int] = Set(1, 2, 3)
    
    scala> val ys: List[Int] = (for(x <- xs) yield 2 * x)(collection.breakOut)
    ys: List[Int] = List(2, 4, 6)
    

    You can convert a Set to a List using one of following ways:

    scala> List.empty[Int] ++ xs
    res0: List[Int] = List(1, 2, 3)
    
    scala> xs.toList
    res1: List[Int] = List(1, 2, 3)
    

    Recommended read: The Architecture of Scala Collections

    4
    votes

    If you use map/flatmap/filter instead of for comprehensions, you can use scala.collection.breakOut to create a different type of collection:

    scala> val result:List[Int] = values.map(2*)(scala.collection.breakOut)
    result: List[Int] = List(2, 4, 6)
    

    If you wanted to build your own collection classes (which is the closest thing to "replicating yield" that makes any sense to me), you should have a look at this tutorial.

    1
    votes

    Try this:

    val values = Set(1, 2, 3)
    val results = for {v <- values} yield (v * 2).toList