Scala函数在列表中丢失变量类型?

Tyc*_*cho 3 generics scala type-erasure akka

背景:

我试图通过动态创建/组合Scala中的匿名函数的部分函数(case ...)来减少代码并改进Akka中的代码重用.

要创建这些部分函数,​​我需要访问函数的参数类型(使用类型参数T),但遗憾的是这受到类型擦除的影响.

我发现使用TypeTags或TypeClasses我可以处理它,这很好.但是,我不是一次一个地将我的功能转换为部分功能,而是希望批量使用.map().

然而,这似乎是失败的; 当通过地图使用该功能时,似乎突然变成T Nothing,导致我的功能功能失常(没有双关语意).

TL; DR:我可以得到lst(0)给String吗?

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
Run Code Online (Sandbox Code Playgroud)

对于Akka好奇,我的实际功能,而不是上述check():

def caseFn[T](fn: T => Unit)(implicit ct: ClassTag[T]): Actor.Receive = {
  case ct(msg: T) => {
    fn(msg)
  }
}
Run Code Online (Sandbox Code Playgroud)

Mar*_*189 5

你可以通过改变来使它工作

val lst = List(fn).map(check)
Run Code Online (Sandbox Code Playgroud)

至

val lst = List(fn).map(check(_))
Run Code Online (Sandbox Code Playgroud)

这里发生了什么?

在这种map(check)情况下,Scala eta-expansion会将method(check)转换为函数,请参阅Scala语言规范版本2.9的 6.26.5 :

6.26.5 Eta扩展

Eta-expansion将方法类型的表达式转换为函数类型的等效表达式.它分两步进行.首先,一个标识e的最大子表达式; 让我们说这些是e_1,...,e_m.对于其中的每一个,都会创建一个新名称x_i.让E"是从替换每个最大子表达式产生的表达e_i在ë由相应的新鲜名称X_I.其次,为方法的每个参数类型T_i创建一个新名称y_i(i = 1,... n).eta转换的结果是:

{ val x_1 = e_1;
  ...
  val x_m = em;
  (y_1: T_1,...,y_n:T_n) => e'(y_1,...,y_n)
}
Run Code Online (Sandbox Code Playgroud)

因此,在map(check)Scala执行eta扩展并且必须推断泛型方法的类型(在eta扩展期间生成)check.由于Scala中类型推断的限制,它将推断Nothing而不是String,因此第一个版本不起作用,而第二个版本不起作用.