zhen zhen - 1 month ago 9
Scala Question

scala map method can not correctly resolve function type in collection with fixed function types

I have the following code,

def obsKPI[T](kpi: Option[T], f: T => Unit) = {
kpi match {
case Some(obsValue) => f(obsValue)
case _ => Unit
}
}

def func1(str:String):Unit = println(str)
def func2(num: Int):Unit = println(num)

//option1: val inputArgs = List((Some("first"),(func1 _)),(Some("third"), (func1 _)))
//option2: val inputArgs = List((Some(456), (func2 _)),(None,(func2 _)))
// option3:
val inputArgs = List((Some("first"),(func1 _)),(Some(456), (func2 _)),(Some("third"), (func1 _)),(None,(func2 _)))

inputArgs.map(x => obsKPI(x._1, x._2))


when running either option 1 or 2 (the inputArgs list contains function of only String=>Unit or Int=>Unit), the code works, but when running option 3, I get an error:

:68: error: type mismatch;
found : Int with String => Unit
required: Any => Unit
inputArgs.map(x => obsKPI(x._1, x._2))
^


Thanks for letting me know what went wrong in here.

Answer

Functions aren't covariant in their parameter types (they are in fact contravariant). This means, that if Foo is a subclass of Bar, Foo => Unit is not a subclass of Bar => Unit (the opposite is true).

In your case, you are trying to coerce func1 and func2 to Any => Unit, but that cannot work, because their types are incompatible - one is String => Unit, and the other one is Int => Unit.

One way to get around this problem, is to use a case class rather than a tuple:

 case class KPI[T](t: Option[T], f: T => Unit)
 def obsKPI(kpi: KPI[_]) = kpi match {
    case KPI(Some(k), f) => f(k) 
    case _ => () // `()` is the value of type Unit. Unit, as you had it is the value of type Unit.type - not what you want at all
 }
 // You can also write the above more concise like this: def obsKPI[T](kpi: KPI[T]) = kpi.t.foreach(kpi.f)

 def func1(str:String) = println(str)
 def func2(num: Int) = println(num)

 val inputArgs = List(KPI(Some("first"),func1 _), KPI(Some(456), func2 _), KPI(Some("third"), func1 _), KPI[Int](None,func2 _))

 inputArgs.foreach(obsKPI) // Could do .map here too, but ending up with a list of ()s is unlikely what you you want.

You can make it look a bit more elegant, if you make your obsKPI into a member of the case class:

case class KPI[T](t: Option[T], f: T => Unit) {
  def obs = t.foreach(f)
}
val inputArgs = List(KPI(Some("first"),func1 _), KPI(Some(456), func2 _), KPI(Some("third"), func1 _), KPI[Int](None,func2 _))

inputArgs.foreach(_.obs)
Comments