注意:我自己提出这个问题,但欢迎提出其他答案。
考虑以下简单方法:
def add[T](x: T, y: T)(implicit num: Numeric[T]) = num.plus(x,y)
我可以使用context bound重写此内容,如下所示
def add[T: Numeric](x: T, y: T) = ??.plus(x,y)
但是如何获取Numeric[T]
类型的实例以便我可以调用plus
方法?
答案 0 :(得分:24)
使用隐式方法
最常见和最通用的方法是使用Predef中定义的implicitly method:
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)
答案 1 :(得分:7)
至少从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
答案 2 :(得分:4)
这个答案描述了另一种方法,它可以产生更易读,自我记录的客户端代码。
<强>动机强>
context
method that I described previously是一个非常通用的解决方案,适用于任何类型的类,无需任何额外的努力。但是,由于两个原因,这可能是不可取的:
当type参数有多个上下文边界时,不能使用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]
最后,客户端代码可以使用Implicitly,如下所示:
def add[T: Numeric](x: T, y: T) = numeric[T].plus(x, y)