注意:我提出这个问题是为了自己回答,但欢迎其他答案。
考虑以下简单方法:
def add[T](x: T, y: T)(implicit num: Numeric[T]) = num.plus(x,y)
我可以使用上下文绑定重写它,如下所示
def add[T: Numeric](x: T, y: T) = ??.plus(x,y)
但是如何获取该Numeric[T]
类型的实例以便调用该plus
方法?
注意:我提出这个问题是为了自己回答,但欢迎其他答案。
考虑以下简单方法:
def add[T](x: T, y: T)(implicit num: Numeric[T]) = num.plus(x,y)
我可以使用上下文绑定重写它,如下所示
def add[T: Numeric](x: T, y: T) = ??.plus(x,y)
但是如何获取该Numeric[T]
类型的实例以便调用该plus
方法?
使用隐式方法
最常见和通用的方法是使用 Predef 中定义的隐式方法:
def add[T: Numeric](x: T, y: T) = implicitly[Numeric[T]].plus(x,y)
显然,这有点冗长,需要重复类型类的名称。
引用证据参数(不要!)
另一种选择是使用编译器自动生成的隐式证据参数的名称:
def add[T: Numeric](x: T, y: T) = evidence$1.plus(x,y)
令人惊讶的是,这种技术甚至是合法的,并且在实践中不应该依赖它,因为证据参数的名称可能会改变。
更高种类的上下文(介绍context
方法)
相反,可以使用该implicitly
方法的增强版本。请注意,隐式方法定义为
def implicitly[T](implicit e: T): T = e
此方法仅依赖于编译器将正确类型的隐式对象从周围范围插入到方法调用中,然后将其返回。我们可以做得更好一点:
def context[C[_], T](implicit e: C[T]) = e
这允许我们将我们的add
方法定义为
def add[T: Numeric](x: T, y: T) = context.plus(x,y)
context
方法类型参数Numeric
和从T
范围推断!不幸的是,在某些情况下这种context
方法不起作用。例如,当一个类型参数具有多个上下文边界或有多个具有不同上下文边界的参数时。我们可以用稍微复杂一点的版本来解决后一个问题:
class Context[T] { def apply[C[_]]()(implicit e: C[T]) = e }
def context[T] = new Context[T]
这个版本要求我们每次都指定类型参数,但是处理多个类型参数。
def add[T: Numeric](x: T, y: T) = context[T]().plus(x,y)
至少从 Scala 2.9 开始,您可以执行以下操作:
import Numeric.Implicits._
def add[T: Numeric](x: T, y: T) = x + y
add(2.8, 0.1) // res1: Double = 2.9
add(1, 2) // res2: Int = 3
此答案描述了另一种方法,该方法可生成更具可读性、自记录的客户端代码。
动机
context
我之前描述的方法是一个非常通用的解决方案,它适用于任何类型类,无需任何额外的努力。但是,由于两个原因,它可能是不可取的:
context
当类型参数具有多个上下文边界时,不能使用该方法,因为编译器无法确定预期的上下文绑定。
对泛型context
方法的引用会损害客户端代码的可读性。
特定于类型类的方法
使用绑定到所需类型类的方法可以使客户端代码更具可读性。这是 Manifest 类型类的标准库中使用的方法:
// definition in Predef
def manifest[T](implicit m: Manifest[T]) = m
// example usage
def getErasure[T: Manifest](x: T) = manifest[T].erasure
推广这种方法
使用特定于类型类的方法的主要缺点是必须为每个类型类定义一个附加方法。我们可以通过以下定义来简化这个过程:
class Implicitly[TC[_]] { def apply[T]()(implicit e: TC[T]) = e }
object Implicitly { def apply[TC[_]] = new Implicitly[TC] }
然后可以为任何类型类定义一个新的特定于类型类的隐式样式方法:
def numeric = Implicitly[Numeric]
// or
val numeric = Implicitly[Numeric]
最后,客户端代码可以隐式使用如下:
def add[T: Numeric](x: T, y: T) = numeric[T].plus(x, y)