Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala functions lose variable type in list?

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)
  }
}
like image 666
Kiara Grouwstra Avatar asked Dec 04 '25 03:12

Kiara Grouwstra


2 Answers

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.

like image 99
Markus1189 Avatar answered Dec 06 '25 15:12

Markus1189


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.

like image 28
thoredge Avatar answered Dec 06 '25 15:12

thoredge