Background:
I'm trying to reduce code and improve code reuse in Akka by dynamically creating/combining partial functions (case ...) from anonymous functions in Scala.
To create these partial functions I need access to the functions' parameter type though (using a type parameter T), but unfortunately this is subject to type erasure.
I found that using TypeTags or TypeClasses I could deal with that, which is great. However, rather than converting my functions into partial functions one at a time, I'd like to do this in batch using .map().
However, this appears to be failing; when using the function through a map, it seems T suddenly becomes Nothing instead, rendering my function dysfunctional (no pun intended).
TL;DR: can I get that lst(0) to give String as well?
import scala.reflect.ClassTag
def fn = (s: String) => {}
def check[T](fn: T => Unit)(implicit ct: ClassTag[T]) = ct
check(fn)
//scala.reflect.ClassTag[String] = java.lang.String
val lst = List(fn).map(check)
lst(0)
//scala.reflect.ClassTag[Nothing] = Nothing
For the Akka-curious, my actual function in question, rather than the above check():
def caseFn[T](fn: T => Unit)(implicit ct: ClassTag[T]): Actor.Receive = {
case ct(msg: T) => {
fn(msg)
}
}
You can get it working by changing
val lst = List(fn).map(check)
to
val lst = List(fn).map(check(_))
What is going on here?
In the map(check) case, Scala does so called eta-expansion to convert the method (check) to a function, see 6.26.5 of the Scala Language Specification Version 2.9:
6.26.5 Eta Expansion
Eta-expansion converts an expression of method type to an equivalent expression of function type. It proceeds in two steps. First, one identifies the maximal sub-expressions of e; let's say these are e_1,...,e_m. For each of these, one creates a fresh name x_i. Let e' be the expression resulting from replacing every maximal subexpression e_i in e by the corresponding fresh name x_i. Second, one creates a fresh name y_i for every argument type T_i of the method (i=1,...n). The result of eta-conversion is then:
{ val x_1 = e_1; ... val x_m = em; (y_1: T_1,...,y_n:T_n) => e'(y_1,...,y_n) }
So in the map(check), Scala performs eta-expansion and has to infer the type (generated during eta-expansion) of the generic method check. Due to limitations of the type inference in Scala, it will infer Nothing instead of String, therefore the first version does not work, while the second does.
If you do it like this it works better:
val lst = List(fn).map(check(_))
// lst: List[scala.reflect.ClassTag[String]] = List(java.lang.String)
Not entirely sure why.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With