cib
cib

Reputation: 2414

Can I get Scala to infer the Option type here?

I want to call a generic function f[X](...), and in my case X happens to be Option[Y]. I try to pass both Some[...] and None where the function expects X, but Scala insists that X be of type Some[Y].

  def flattenOptionMap[A, B](input : Map[A, Option[B]]) : Option[Map[A, B]] = {
    input.foldLeft[Option[Map[A,B]]] (Some(Map.empty)) {
      case (_, (_, None)) => None
      case (None, (_, _)) => None
      case (Some(acc), (key, Some(value))) => Some(acc + (key -> value))
    }
  }

In this example, I had to explicitly specify that Option[Map[A,B]] should be used as generic type for foldLeft. All the necessary type information is already contained in the context, and typing cumbersome types like Option[Map[A,B]] more often than necessary in my opinion drastically reduces readability of my code.

Is there some way to get Scala to infer the type after all, or otherwise avoid copy-pasting the whole type?

Upvotes: 2

Views: 291

Answers (2)

beefyhalo
beefyhalo

Reputation: 1821

If your goal is to improve readability, and to get help from the compiler to infer types, try:

def flattenOptionMap[A, B](input: Map[A, Option[B]]) =
  input.foldLeft(Option(Map.empty[A, B])) {
    case (_, (_, None))                  => None
    case (None, (_, _))                  => None
    case (Some(acc), (key, Some(value))) => Some(acc + (key -> value))
  }

or even better:

def flattenOptionMap[A, B](input: Map[A, Option[B]]) =
  input.foldLeft(Option(Map.empty[A, B])) {
    case (Some(acc), (key, Some(value))) => Some(acc + (key -> value))
    case _                               => None
  }

Or even better (in my opinion): Peter Neyens's answer. Use sequence from your Traverse instance.

Upvotes: 4

Peter Neyens
Peter Neyens

Reputation: 9820

When you use Option(Map.empty[A, B]) as the start value of your foldLeft, Scala will infer the correct type as I wrote in the comments (and beefyhalo in his answer).

I would like to add, that if you are open to using Scalaz, you can just use the sequence function.

import scalaz._
import Scalaz._ 

val mapSome = Map(1 -> Some("a"), 2 -> Some("b"))
val mapNone = Map(1 -> Some("a"), 2 -> Some("b"), 3 -> None)

mapSome.sequence
flattenOptionMap(mapSome)
// Option[Map[Int,String]] = Some(Map(1 -> a, 2 -> b))

mapNone.sequence
flattenOptionMap(mapNone)
// Option[Map[Int,String]] = None

Upvotes: 5

Related Questions