Scala 如何获取与上下文绑定关联的类型类的实例?
注:我提出这个问题是为了自己回答,但欢迎其他答案 考虑以下简单方法:Scala 如何获取与上下文绑定关联的类型类的实例?,scala,typeclass,context-bound,Scala,Typeclass,Context Bound,注:我提出这个问题是为了自己回答,但欢迎其他答案 考虑以下简单方法: 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:
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)
令人惊讶的是,这种技术甚至是合法的,在实践中不应该依赖它,因为证据参数的名称可能会改变
更高级的上下文(介绍上下文
方法)
相反,可以隐式地使用增强版的方法。请注意,隐式方法定义为
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
是从范围中推断出来的!不幸的是,在某些情况下,此上下文方法无法工作。例如,当类型参数具有多个上下文边界或存在多个具有不同上下文边界的参数时。我们可以用稍微复杂一点的版本来解决后一个问题:
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
这个答案描述了另一种方法,这种方法可以产生更可读的、自文档化的客户机代码
动机
这是一个非常通用的解决方案,可用于任何类型类,无需任何额外的工作。然而,出于两个原因,这可能是不可取的:
- 当type参数有多个上下文边界时,不能使用
context
方法,因为编译器无法确定要使用哪个上下文边界
- 对泛型
上下文方法的引用会损害客户端代码的可读性
类型类特定方法
使用绑定到所需类型类的方法可以使客户机代码更具可读性。这是清单类型类的标准库中使用的方法:
// definition in Predef
def manifest[T](implicit m: Manifest[T]) = m
// example usage
def getErasure[T: Manifest](x: T) = manifest[T].erasure
def numeric = Implicitly[Numeric]
// or
val numeric = Implicitly[Numeric]
推广这种方法
使用特定于类型类的方法的主要缺点是必须为每个类型类定义一个附加方法。我们可以通过以下定义简化此过程:
class Implicitly[TC[_]] { def apply[T]()(implicit e: TC[T]) = e }
object Implicitly { def apply[TC[_]] = new Implicitly[TC] }
然后,可以为任何类型类定义新的类型类特定的隐式样式方法:
// definition in Predef
def manifest[T](implicit m: Manifest[T]) = m
// example usage
def getErasure[T: Manifest](x: T) = manifest[T].erasure
def numeric = Implicitly[Numeric]
// or
val numeric = Implicitly[Numeric]
最后,客户端代码可以按如下方式隐式使用:
def add[T: Numeric](x: T, y: T) = numeric[T].plus(x, y)
使用context
方法进行聪明的黑客攻击!天哪,如果我认为有人用这种方式依赖证据参数的名称,我会每周左右更改一次。。。另外,这种技术在我的管辖范围内是不合法的,但在你居住的地方可能法律不同。@2.8.1 REPL中即兴使用,但我很高兴听到它在主干中是不合法的(假设这是你的管辖范围)。:-)如果能够编写def add[T:Numeric](x:T,y:T)=加上(x,y)