背景
我试图通过从Scala中的匿名函数动态创建/组合部分函数(case ...)来减少代码并改进Akka中的代码重用。
要创建这些部分功能,我需要访问功能'参数类型虽然(使用类型参数T),但不幸的是这取决于类型擦除。
我发现使用TypeTag
或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好奇,我的实际功能,而不是上面的check()
:
def caseFn[T](fn: T => Unit)(implicit ct: ClassTag[T]): Actor.Receive = {
case ct(msg: T) => {
fn(msg)
}
}
答案 0 :(得分:5)
您可以通过更改
来使其正常工作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扩展
Eta-expansion将方法类型的表达式转换为函数类型的等效表达式。它分两步进行。首先,一个标识 e 的最大子表达式;让我们说这些是 e_1 ,..., e_m 。对于其中的每一个,都会创建一个新名称 x_i 。让 e' 成为将 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) }
因此,在map(check)
中,Scala执行 eta-expansion ,并且必须推断泛型方法check
的类型(在eta扩展期间生成)。由于Scala中类型推断的限制,它将推断Nothing
而不是String
,因此第一个版本不起作用,而第二个版本不起作用。
答案 1 :(得分:1)
如果你这样做会更好:
val lst = List(fn).map(check(_))
// lst: List[scala.reflect.ClassTag[String]] = List(java.lang.String)
不完全确定原因。
答案 2 :(得分:1)
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)