【问题标题】:How do I get an instance of the type class associated with a context bound?如何获取与上下文绑定关联的类型类的实例?
【发布时间】:2011-05-21 08:37:45
【问题描述】:

注意:我提出这个问题是为了自己回答,但欢迎其他答案。

考虑以下简单方法:

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 方法?

【问题讨论】:

    标签: scala typeclass context-bound


    【解决方案1】:

    使用隐式方法

    最常见和通用的方法是使用 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方法类型参数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)
    

    【讨论】:

    • 巧妙地使用context 方法!
    • 男孩,如果我认为有人依赖于证据参数的名称,我会每周更改一次左右......而且,该技术在我的管辖范围内是不合法的,但是也许您居住的地方的法律有所不同。
    • @extempore 它适用于 2.8.1 REPL,但我很高兴听到它在中继中不合法(假设这是您的管辖范围)。 :-)
    • 能写def add[T: Numeric](x: T, y: T) = _.plus(x,y) 会很高兴@
    【解决方案2】:

    此答案描述了另一种方法,该方法可生成更具可读性、自记录的客户端代码。

    动机

    context method that I described previously 是一个非常通用的解决方案,适用于任何类型类,无需任何额外工作。但是,这可能是不可取的,原因有两个:

    • 当类型参数有多个上下文边界时,不能使用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)
    

    【讨论】:

      【解决方案3】:

      至少从 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
      

      【讨论】:

        猜你喜欢
        • 2021-10-11
        • 2014-11-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-01-03
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多