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

Scala functions lose variable type in list?

背景:

我正在尝试通过动态 creating/combining 来自 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

对于Akka-curious,我的实际函数有问题,而不是上面的check()

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

如果你这样做效果会更好:

val lst = List(fn).map(check(_))
// lst: List[scala.reflect.ClassTag[String]] = List(java.lang.String)

不太清楚为什么。

scala> check _
res19: (Nothing => Unit) => scala.reflect.ClassTag[Nothing] = <function1>

scala> check(_: String => Unit)
res20: (String => Unit) => scala.reflect.ClassTag[String] = <function1>

scala> List(fn).map(check)
res21: List[scala.reflect.ClassTag[Nothing]] = List(Nothing)

scala> List(fn).map(check _)
res22: List[scala.reflect.ClassTag[Nothing]] = List(Nothing)

scala> List(fn).map(check(_))
res23: List[scala.reflect.ClassTag[String]] = List(java.lang.String)

您可以通过更改

使其正常工作
val lst = List(fn).map(check)

val lst = List(fn).map(check(_))

这是怎么回事?

map(check)的情况下,Scala调用eta-expansion将方法(check)转换为函数,参见Scala Language Specification Version 2.9的6.26.5:

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)
}

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