以简单的方式来说,什么是上下文界限和视图界限,它们之间有什么区别?
一些易于理解的例子也将是很好的补充!
我认为这个问题已经被问过了,但是如果是的话,在“相关”栏中并没有明显的问题。所以,这里是:
视图界定(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]
定义了方法<(other: A): Boolean
,所以可以使用表达式a < b
。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)
在参数化类型上进行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
。
下面我们还会看到另一种实现方式。
鉴于它们的定义,视图界定和上下文界定都是使用隐式参数实现的,这并不令人意外。实际上,我展示的语法只是真正发生的事情的语法糖。请参见下面的反糖果操作:
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 设计模式,通过该模式可以向现有的类“添加”方法,用于在需要以某种方式返回原始类型的情况下使用。如果您不需要以任何方式返回该类型,则不需要使用 View Bounds。
View Bounds 的典型用法是处理 Ordered
。请注意,例如 Int
并不是 Ordered
,尽管存在隐式转换。以前给出的示例需要使用 View Bounds,因为它返回非转换类型:
def f[A <% Ordered[A]](a: A, b: A): A = if (a < b) a else b
如果我要返回另一种类型,那么就不需要视图界限(view bounds)了。但是,如果没有视图界限,这个示例将无法工作:
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
上下文界定通常在所谓的类型类模式(typeclass pattern)中使用,以引用Haskell的类型类(type classes)。基本上,这个模式通过通过一种隐式的适配器模式来实现功能,而不是通过继承。
典型的例子是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
用法,它需要使用具体类型来初始化新数组。
类型类模式中的上下文界定更有可能被你自己的类使用,因为它们可以实现责任的分离,而视图界定则可以通过良好的设计避免在你自己的代码中使用(它主要用于绕过别人的设计)。
虽然很长一段时间以来都已经可能使用上下文界定,但其在2010年真正流行起来,并且现在在Scala的大多数重要库和框架中都能以某种程度找到。然而,最极端的使用例子是Scalaz库,它将Haskell的许多功能带到了Scala中。我建议阅读有关类型类模式的内容,以更熟悉所有可以使用它的方式。
编辑
相关问题: