简单地说,什么是上下文和视图边界,它们之间有什么区别?
一些简单易懂的例子也会很棒!
简单地说,什么是上下文和视图边界,它们之间有什么区别?
一些简单易懂的例子也会很棒!
我以为这个问题已经被问过了,但是,如果是这样的话,这个问题在“相关”栏中并不明显。所以,就是这样:
什么是视图边界?
视图绑定是Scala中引入的一种机制,可以像使用类型b一样使用类型A。典型的语法如下:
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]定义了方法<(other: A): Boolean,所以我可以使用表达式A < b。
请注意,视图边界已弃用,您应该避免使用它们。
什么是上下文界限?
上下文边界是在Scala 2.8.0中引入的,通常与所谓的类型类模式一起使用,这是一种模拟Haskell类型类提供的功能的代码模式,尽管是以更详细的方式使用。
虽然视图绑定可以与简单类型一起使用(例如,a <% 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)
参数化类型上的数组初始化需要一个ClassManifest可用,这是由于与类型擦除和数组的非擦除性质有关的神秘原因。
库中另一个非常常见的例子更加复杂:
def f[A : Ordering](a: A, b: A) = implicitly[Ordering[A]].compare(a, b)
这里,隐式用于检索我们想要的隐式值,类型为ordered [A],该类定义了方法compare(A: A, b: A): Int。
我们将在下面看到另一种方法。
视图边界和上下文边界是如何实现的?
鉴于视图边界和上下文边界的定义,它们都是用隐式参数实现的,这并不奇怪。实际上,我所展示的语法是用来描述实际情况的语法糖。看看他们是如何去糖的:
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)
视图边界的用途是什么?
视图边界主要用于利用pimp my库模式,在您希望以某种方式返回原始类型的情况下,可以通过该模式向现有类“添加”方法。如果不需要以任何方式返回该类型,则不需要视图绑定。
视图绑定使用的经典示例是处理Ordered。例如,注意Int不是有序的,尽管有一个隐式转换。前面给出的例子需要一个视图绑定,因为它返回非转换类型:
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之外,库中最常见的用法是处理String和Array,它们是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
上下文边界用于什么?
上下文边界主要用于众所周知的类型类模式中,作为Haskell类型类的引用。基本上,该模式通过一种隐式适配器模式提供功能,从而实现了继承的替代方案。
经典的例子是Scala 2.8的Ordered,它取代了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
}
它们利用了ordered内部的一些隐式转换来支持传统的操作符样式。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的功能。我建议仔细阅读类型类模式,以更好地了解它的所有使用方式。
EDIT
相关问题:
关于隐式的类型、起源和优先级的讨论 值得一提的是链接