简单地说,什么是上下文和视图边界,它们之间有什么区别?
一些简单易懂的例子也会很棒!
我以为这个问题已经被问过了,但是,如果是这样的话,这个问题在“相关”栏中并不明显。所以,就是这样:
视图绑定是Scala中引入的一种机制,用于允许使用某些类型A 好像,即某些类型B。典型的语法如下:
A
B
def f[A <% B](a: A) = a.bMethod
换句话说,A应该有一个到B的隐式转换可用,这样就可以在类型为A的对象上调用B方法。在标准库(至少在Scala 2.8.0之前)中,视图边界最常见的用法是Ordered,如下所示:
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。
Ordered[A]
<(other: A): Boolean
a < b
请注意视图边界已弃用,你应该避免它们。
上下文边界是在Scala 2.8.0中引入的,通常与所谓的类型类模式一起使用,类型类模式是一种模拟Haskell类型类提供的功能的代码模式,尽管以更详细的方式使用。
虽然视图绑定可以与简单类型一起使用(例如,A <% String),但上下文绑定需要参数化类型,例如上面的Ordered[A],但与String不同。
A <% String
String
上下文边界描述的是隐式价值,而不是视图边界的隐式转换。它用于声明对于某些类型A,有一个类型B[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可用,原因与类型擦除和数组的非擦除性质有关。
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。
implicitly
Ordering[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)
视图边界主要用于利用皮条客我的图书馆模式,在希望以某种方式返回原始类型的情况下,可以通过该模式向现有类“添加”方法。如果不需要以任何方式返回该类型,则不需要视图绑定。
视图绑定使用的经典示例是处理Ordered。例如,注意Int不是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不需要知道它。
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也是如此。
WrappedString
即使该类型仅用作返回类型的类型参数,也会发生同样的事情:
def f[A <% Ordered[A]](xs: A*): Seq[A] = xs.toSeq.sorted
上下文边界主要用于众所周知的typeclass模式中,作为Haskell类型类的引用。基本上,该模式通过一种隐式适配器模式提供功能,从而实现了继承的替代方案。
经典的例子是Scala 2.8的Ordering,它在Scala的整个库中取代了Ordered。用法是:
Ordering
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:
Numeric
def f[A : Numeric](a: A, b: A) = implicitly[Numeric[A]].plus(a, b)
一个更复杂的例子是CanBuildFrom的新集合用法,但关于这个问题已经有很长的答案了,所以我在这里就不赘言了。并且,如前所述,有ClassManifest的用法,它需要初始化没有具体类型的新数组。
CanBuildFrom
与typeclass模式绑定的上下文更有可能被您自己的类使用,因为它们支持关注点分离,而视图边界可以通过良好的设计在您自己的代码中避免(它主要用于绕过其他人的设计)。
虽然上下文边界的使用已经有很长一段时间了,但它在2010年才真正开始流行起来,现在在某种程度上可以在大多数Scala最重要的库和框架中找到。不过,它使用的最极端的例子是Scalaz库,它为Scala带来了大量Haskell的功能。我建议仔细阅读类型类模式,以更好地了解它的所有使用方式。
编辑
相关问题: