Lambda 类型推断和隐式转换
Lambda type inference and implicit conversions
我定义了以下 class:
class TransparentFunction1[-T1, +R1](val func : T1 => R1, val text : String) {
@inline
def apply(t : T1) = func(t)
override def toString = text
}
基本上,TransparentFunction1
只是 Function1
的包装器,它提供了一个人类可读的文本字段来描述函数是什么。
我想定义一个隐式转换,可以将任何 Function1 转换为 TransparentFunction1
,将函数的代码传递给文本参数。
我已经使用宏定义了这样一个隐式转换:
implicit def transparentFunction1[T1, R1](expression : T1 => R1) : TransparentFunction1[T1, R1] = macro Macros.transparentImpl[T1, R1, TransparentFunction1[T1, R1]]
object Macros {
def transparentImpl[T : context.WeakTypeTag, U : context.WeakTypeTag, V : context.WeakTypeTag](context : scala.reflect.macros.whitebox.Context) (expression : context.Expr[T => U]) : context.Expr[V] = {
import context.universe._
context.Expr[V](
Apply(
Select(
New(
TypeTree(
appliedType(weakTypeOf[V].typeConstructor, weakTypeOf[T] :: weakTypeOf[U] :: Nil)
)
),
termNames.CONSTRUCTOR
),
List(expression.tree, Literal(Constant(expression.tree.toString)))
)
)
}
}
这行得通。但是,它会导致类型推断出现问题。
例如,如果我尝试调用一个名为 "map" 的方法,该方法采用 TransparentFunction1[Int, Int]
类型的参数,如下所示:
map(_ + 2)
我收到错误 "missing parameter type for expanded function",而如果 map 的参数类型只是 Int => Int
,则类型推断工作正常。
有没有办法修复宏以便类型推断继续工作?
我认为没有办法做到这一点。前段时间,我曾尝试对 Scala.js 中的 js.FunctionN
做同样的事情(例如,将 T1 => R
隐式转换为 js.Function1[T1, R]
),但我永远做不到使类型推断适用于 lambda 的参数。好像是不可能的,可惜。
要解决此问题,您只需要 TransparentFunction1
扩展 Function1
(这看起来很自然,因为 TransparentFunction1
在概念上非常类似于 Function1
,它只是添加自定义 toString
但应作为普通函数使用):
class TransparentFunction1[-T1, +R1](val func : T1 => R1, val text : String) extends (T1 => R1){
@inline
def apply(t : T1) = func(t)
override def toString = text
}
对于定义一个 不 扩展 Function1
的类似 class 的函数,我只能看到少数几个原因。在我的脑海中,主要原因是当你的 class 被设计为用作隐式值(例如类型 class),并且你不希望编译器自动使用那些隐式值作为隐式转换(如果它扩展 Function1
,它就会这样做)。这里似乎不是这种情况,因此 TransparentFunction1
扩展 Function1
似乎是正确的做法。
我定义了以下 class:
class TransparentFunction1[-T1, +R1](val func : T1 => R1, val text : String) {
@inline
def apply(t : T1) = func(t)
override def toString = text
}
基本上,TransparentFunction1
只是 Function1
的包装器,它提供了一个人类可读的文本字段来描述函数是什么。
我想定义一个隐式转换,可以将任何 Function1 转换为 TransparentFunction1
,将函数的代码传递给文本参数。
我已经使用宏定义了这样一个隐式转换:
implicit def transparentFunction1[T1, R1](expression : T1 => R1) : TransparentFunction1[T1, R1] = macro Macros.transparentImpl[T1, R1, TransparentFunction1[T1, R1]]
object Macros {
def transparentImpl[T : context.WeakTypeTag, U : context.WeakTypeTag, V : context.WeakTypeTag](context : scala.reflect.macros.whitebox.Context) (expression : context.Expr[T => U]) : context.Expr[V] = {
import context.universe._
context.Expr[V](
Apply(
Select(
New(
TypeTree(
appliedType(weakTypeOf[V].typeConstructor, weakTypeOf[T] :: weakTypeOf[U] :: Nil)
)
),
termNames.CONSTRUCTOR
),
List(expression.tree, Literal(Constant(expression.tree.toString)))
)
)
}
}
这行得通。但是,它会导致类型推断出现问题。
例如,如果我尝试调用一个名为 "map" 的方法,该方法采用 TransparentFunction1[Int, Int]
类型的参数,如下所示:
map(_ + 2)
我收到错误 "missing parameter type for expanded function",而如果 map 的参数类型只是 Int => Int
,则类型推断工作正常。
有没有办法修复宏以便类型推断继续工作?
我认为没有办法做到这一点。前段时间,我曾尝试对 Scala.js 中的 js.FunctionN
做同样的事情(例如,将 T1 => R
隐式转换为 js.Function1[T1, R]
),但我永远做不到使类型推断适用于 lambda 的参数。好像是不可能的,可惜。
要解决此问题,您只需要 TransparentFunction1
扩展 Function1
(这看起来很自然,因为 TransparentFunction1
在概念上非常类似于 Function1
,它只是添加自定义 toString
但应作为普通函数使用):
class TransparentFunction1[-T1, +R1](val func : T1 => R1, val text : String) extends (T1 => R1){
@inline
def apply(t : T1) = func(t)
override def toString = text
}
对于定义一个 不 扩展 Function1
的类似 class 的函数,我只能看到少数几个原因。在我的脑海中,主要原因是当你的 class 被设计为用作隐式值(例如类型 class),并且你不希望编译器自动使用那些隐式值作为隐式转换(如果它扩展 Function1
,它就会这样做)。这里似乎不是这种情况,因此 TransparentFunction1
扩展 Function1
似乎是正确的做法。