Scala 函数在列表中丢失变量类型?
Scala functions lose variable type in list?
背景:
我正在尝试通过动态 creating/combining 来自 Scala 中匿名函数的部分函数(case ...)来减少代码并提高 Akka 中的代码重用。
要创建这些部分函数,我需要访问函数的参数类型(使用类型参数 T),但不幸的是,这会受到类型擦除的影响。
我发现使用 TypeTag
s 或 TypeClass
es 我可以解决这个问题,这很棒。但是,我不想一次将我的函数转换为部分函数,而是想使用 .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
,因此第一个版本不起作用,而第二个版本可以。
背景:
我正在尝试通过动态 creating/combining 来自 Scala 中匿名函数的部分函数(case ...)来减少代码并提高 Akka 中的代码重用。
要创建这些部分函数,我需要访问函数的参数类型(使用类型参数 T),但不幸的是,这会受到类型擦除的影响。
我发现使用 TypeTag
s 或 TypeClass
es 我可以解决这个问题,这很棒。但是,我不想一次将我的函数转换为部分函数,而是想使用 .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
,因此第一个版本不起作用,而第二个版本可以。