【问题标题】:What are Scala context and view bounds?什么是 Scala 上下文和视图边界?
【发布时间】:2010-12-16 22:22:20
【问题描述】:

简单来说,什么是上下文和视图边界,它们之间有什么区别?

一些易于理解的例子也很棒!

【问题讨论】:

    标签: scala implicits


    【解决方案1】:

    我以为这已经被问过了,但是,如果是这样,那么问题在“相关”栏中并不明显。所以,这里是:

    什么是视图绑定?

    view bound 是 Scala 中引入的一种机制,允许使用某种类型的 A 就好像它是某种类型的 B。典型的语法是这样的:

    def f[A <% B](a: A) = a.bMethod
    

    换句话说,A 应该有一个到B 的隐式转换可用,以便可以在A 类型的对象上调用B 方法。标准库(无论如何,在 Scala 2.8.0 之前)中视图边界最常见的用法是 Ordered,如下所示:

    def f[A <% Ordered[A]](a: A, b: A) = if (a < b) a else b
    

    因为可以将A 转换为Ordered[A],并且因为Ordered[A] 定义了方法&lt;(other: A): Boolean,所以我可以使用表达式a &lt; b

    请注意view bounds are deprecated,你应该避开他们。

    什么是上下文绑定?

    上下文边界是在 Scala 2.8.0 中引入的,通常与所谓的类型类模式一起使用,这是一种模拟 Haskell 类型类提供的功能的代码模式,尽管在更详细的方式。

    虽然视图绑定可以用于简单类型(例如,A &lt;% String),但上下文绑定需要参数化类型,例如上面的Ordered[A],但与String 不同。

    上下文绑定描述了一个隐式,而不是视图绑定的隐式转换。它用于声明对于某些类型A,有一个类型为B[A] 的隐式值可用。语法如下:

    def f[A : B](a: A) = g(a) // where g requires an implicit value of type B[A]
    

    这比视图绑定更令人困惑,因为并不清楚如何使用它。 Scala 中的常见用法示例如下:

    def f[A : ClassManifest](n: Int) = new Array[A](n)
    

    参数化类型的Array 初始化需要ClassManifest 可用,原因与类型擦除和数组的非擦除性质有关。

    库中另一个非常常见的例子有点复杂:

    def f[A : Ordering](a: A, b: A) = implicitly[Ordering[A]].compare(a, b)
    

    这里,implicitly 用于检索我们想要的隐式值,Ordering[A] 类型之一,该类定义了方法compare(a: A, b: A): Int

    我们将在下面看到另一种方法。

    View Bounds 和 Context Bounds 是如何实现的?

    鉴于它们的定义,视图边界和上下文边界都是使用隐式参数实现的,这不足为奇。实际上,我展示的语法是真正发生的事情的语法糖。看看下面它们是如何脱糖的:

    def f[A <% B](a: A) = a.bMethod
    def f[A](a: A)(implicit ev: A => B) = a.bMethod
    
    def g[A : B](a: A) = h(a)
    def g[A](a: A)(implicit ev: B[A]) = h(a)
    

    因此,很自然地,我们可以用完整的语法编写它们,这对于上下文边界特别有用:

    def f[A](a: A, b: A)(implicit ord: Ordering[A]) = ord.compare(a, b)
    

    视图边界有什么用?

    View bounds 主要用于利用 pimp my library 模式,在您想以某种方式返回原始类型的情况下,通过该模式将方法“添加”到现有类。如果您不需要以任何方式返回该类型,那么您不需要绑定视图。

    视图绑定使用的经典示例是处理Ordered。请注意,Int 不是 Ordered,例如,尽管存在隐式转换。前面给出的示例需要一个视图绑定,因为它返回未转换的类型:

    def f[A <% Ordered[A]](a: A, b: A): A = if (a < b) a else b
    

    如果没有视图边界,此示例将无法运行。但是,如果我要返回另一种类型,则不再需要绑定视图:

    def f[A](a: Ordered[A], b: A): Boolean = a < b
    

    此处的转换(如果需要)发生在我将参数传递给f 之前,因此f 不需要知道它。

    除了Ordered,库中最常见的用法是处理StringArray,它们是Java 类,就像它们是Scala 集合一样。例如:

    def f[CC <% Traversable[_]](a: CC, b: CC): CC = if (a.size < b.size) a else b
    

    如果尝试在没有视图边界的情况下执行此操作,String 的返回类型将是 WrappedString(Scala 2.8),Array 的返回类型也是如此。

    即使该类型仅用作返回类型的类型参数,也会发生同样的事情:

    def f[A <% Ordered[A]](xs: A*): Seq[A] = xs.toSeq.sorted
    

    上下文边界有什么用?

    上下文边界主要用于被称为 typeclass 模式,作为对 Haskell 类型类的引用。基本上,这种模式通过一种隐式适配器模式提供功能,实现了继承的替代方案。

    经典示例是 Scala 2.8 的 Ordering,它在整个 Scala 库中替换了 Ordered。用法是:

    def f[A : Ordering](a: A, b: A) = if (implicitly[Ordering[A]].lt(a, b)) a else b
    

    虽然你通常会看到这样写:

    def f[A](a: A, b: A)(implicit ord: Ordering[A]) = {
        import ord.mkOrderingOps
        if (a < b) a else b
    }
    

    这利用了Ordering 内部的一些隐式转换,这些转换支持传统的运算符样式。 Scala 2.8 中的另一个例子是Numeric:

    def f[A : Numeric](a: A, b: A) = implicitly[Numeric[A]].plus(a, b)
    

    一个更复杂的例子是CanBuildFrom 的新集合用法,但是已经有一个很长的答案,所以我会在这里避免它。而且,如前所述,还有 ClassManifest 用法,它是初始化没有具体类型的新数组所必需的。

    与 typeclass 模式绑定的上下文更有可能被您自己的类使用,因为它们可以分离关注点,而通过良好的设计可以在您自己的代码中避免视图边界(它主要用于绕过别人的设计)。

    尽管已经有很长一段时间了,但上下文边界的使用在 2010 年才真正起飞,现在在 Scala 的大多数最重要的库和框架中都在某种程度上发现了它。然而,它使用最极端的例子是 Scalaz 库,它为 Scala 带来了 Haskell 的强大功能。我建议阅读类型类模式,以更了解它的所有使用方式。

    编辑

    感兴趣的相关问题:

    【讨论】:

    • 非常感谢。我知道这个问题之前已经回答过了,也许我当时没有仔细阅读,但是你在这里的解释是我见过的最清楚的。所以,再次感谢您。
    • @chrsan 我又添加了两个部分,详细介绍了每个部分的用途。
    • 我认为这是一个很好的解释。如果您同意,我想为我的德语博客 (dgronau.wordpress.com) 翻译此内容。
    • 这是迄今为止我找到的关于这个主题的最好和最全面的解释。真的非常感谢!
    • Sooo,你的 Scala 书什么时候出版,在哪里可以买到 :)
    猜你喜欢
    • 2017-02-01
    • 2011-10-13
    • 1970-01-01
    • 1970-01-01
    • 2011-08-28
    • 2015-10-08
    • 1970-01-01
    • 2011-02-28
    • 2012-03-22
    相关资源
    最近更新 更多