cool breeze cool breeze - 1 month ago 16
Scala Question

Trying to return a Map but an Iterable is currently being returned

Why is my val

pairOpt
a
Option[Option[String,String]]
?

I am trying to have it so it returns
Option[(String, String)]
.

def blah(..): Map[String, String] = {

val map: Map[String, String] = //
val boolTry = Try(map.getOrElse("key1", "").trim.toBoolean)
val intTry = Try(map.getOrElse("key2, "").trim.toInt)

val pairOpt: Option[Option[(String, String)]] = for {
b <- boolTry.toOption
i <- intTry.toOption
} yield {
val res: Option[String] = (b, i) match {
case (true, 1) => Some("a")
case (false, 2 | 3 | 7) => Some("b")
case (true, 5 | 9 | 11) => Some("c")
case _ => None
}
res.map("foo" -> _)
}

map ++ pairOpt // map + ("foo" -> "c")

}


The return value is also currenly
Iterable[Product with Serializable]
when I want it to be
Map[String,String]
.

What am I missing here?

Answer

You get an Option[Option[..]] because you have two "layers" of "optionality" here that you have to flatten:

  • If one of boolTry or intTry is a Failure - you'll get None
  • Else, if they're both Success but their values don't match anything, you'll get Some(None)
  • Otherwise, you'll get Some(Some(..))

More generally, given a opt: Option[V], the type of an expression of the form:

for {
   x <- opt
   .. 
} yield {
   val y: T
   y
}

is Option[T] - because it translates into opt.flatMap(...).map(...) which preserves the "external" structure (be it an Option, List, Seq etc.). In your case, T = Option[(String, String)], so the result has the type Option[Option[(String, String)]]. To fix this - you can use flatten:

val pairOpt: Option[(String, String)] = (for {
    b <- boolTry.toOption
    i <- intTry.toOption
  } yield {
    // same as you did... 
  }).flatten 

Which would also fix the issue with the method's return type (now map ++ pairOpt will have the type Map[String, String] as expected).

To avoid this call to flatten - perhaps a cleaner way to achieve the same would be:

val maybeTuple: Option[(Boolean, Int)] = boolTry.flatMap(b => intTry.map((b, _))).toOption

val pairOpt: Option[(String, String)] = maybeTuple.flatMap {
  case (true, 1) => Some("a")
  case (false, 2 | 3 | 7) => Some("b")
  case (true, 5 | 9 | 11) => Some("c")
  case _ => None
}.map("foo" -> _)