Search code examples
scalaoption-typefor-comprehension

For comprehension with optional collection iteration and yield


I am not very versed in Scala and sincerily I found the documentation hard to try to figure this out, but I was wondering if someone could explain to me why the difference in compilation of the following statements:

I can easily iterate over a set of strings and yield the elements.

scala> for(name <- Set("asd", "123")) yield name
val res2: scala.collection.immutable.Set[String] = Set(asd, 123)

But i can't do it inline if the Set is inside an Option

scala> for(names <- Some(Set("asd", "123")); name <- names) yield (name)
                                              ^
   error: type mismatch;
    found   : scala.collection.immutable.Set[String]
    required: Option[?]

Solution

  • It happens because of for-yield is just syntactic sugar for flatMap, map and withFilter functions. So, your code:

    for(names <- Some(Set("asd", "123")); name <- names) yield (name)
    

    actually is the same as:

    Some(Set("asd", "123")).flatMap{ names: Set[String] => 
      names.flatMap{ name: String => 
        name // return type is String, but Set[(of some )B] is expected
      }
    }// return type is Set[String] but Option[(of some) C] is expected
    

    look at the Option flatMap function:

    @inline final def flatMap[B](f: A => Option[B]): Option[B]
    

    but your f returns Set[String]

    as a result, compiler tell you about type missmatch (Set[String] != Option[?]):

    error: type mismatch;
        found   : scala.collection.immutable.Set[String]
        required: Option[?]
    

    you should remember about type of the first statement in for-yield construction. In your case it's names <- Some(Set("asd", "123")). It has type Option[Set[String]], so you should use only Option[T] in x <- yourNextStatement lines (x should has Option[T] type).

    In conclusion: Be careful with mixing different container types in for-yield constructions. If you have some problems, just try to unwrap your for-yield into combination of flatMap, map, withFilter functions.

    If you want to mix containers in for-yeild, you should start another for-yield for each-container type. For example:

    for { 
      names <- Some(Set("asd", "123"))
      // will return Option[String]
      reducedNames <- (for {
        name <- names // here will be used Seq flatMap function, not Option
      } yield (name + "some_suffix"))
        .reduceLeftOption(_ + _) // here we will get Option[String] from Seq[String]
    } yield reducedNames // will return Option[String]