如何获取与上下文限定相关联的类型类实例?

15
注意:我提出这个问题是为了自己回答,但欢迎其他答案。
考虑以下简单方法:
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方法?

3个回答

24

使用隐式方法

最常见和通用的方法是使用在Predef中定义的implicitly方法

def add[T: Numeric](x: T, y: T) = implicitly[Numeric[T]].plus(x,y)

很明显,这样有些啰嗦,并且需要重复类型类的名称。
引用evidence参数(不要这么做!)
另一种选择是使用编译器自动生成的隐式evidence参数的名称:
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方法的类型参数NumericT是从作用域中推断出来的!不幸的是,在某些情况下,这个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)

5
context方法的聪明技巧! - Daniel Spiewak
8
伙计,如果我发现有人那样依赖证据参数的名称,我会每周进行一次更改……此外,在我的司法辖区内这种技术是不合法的,但也许在你所居住的地方法律有所不同。 - psp
@extempore 它在2.8.1 REPL中可以工作,但我很高兴听到它在主干线上是不合法的(假设那是你的管辖范围)。 :-) - Aaron Novstrup
希望能够这样写:def add[T: Numeric](x: T, y: T) = _.plus(x,y) - shellholic

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

4
这个答案描述了另一种方法,可以产生更易读、自我记录的客户端代码。
动机
我之前描述的 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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接