什么是Scala中的上下文界定和视图界定?

288

以简单的方式来说,什么是上下文界限和视图界限,它们之间有什么区别?

一些易于理解的例子也将是很好的补充!

1个回答

503

我认为这个问题已经被问过了,但是如果是的话,在“相关”栏中并没有明显的问题。所以,这里是:

什么是视图界定(View Bound)?

视图界定(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
请注意,视图界限已被弃用,应该避免使用它们。
什么是上下文界定?
上下文界定在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)

在参数化类型上进行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 的作用是什么?

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外,该库最常见的用法是处理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

什么是上下文界定(Context Bounds),它有什么用途?

上下文界定通常在所谓的类型类模式(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中。我建议阅读有关类型类模式的内容,以更熟悉所有可以使用它的方式。

编辑

相关问题:


3
@chrsan 我添加了两个更多的部分,更详细地介绍了每个部分的使用场景。 - Daniel C. Sobral
2
我认为这是一个非常好的解释。如果您同意的话,我想将其翻译成德语并发布在我的博客(dgronau.wordpress.com)上。 - Landei
1
@Landei 我对此没有问题,但是Stack Overflow网站内的内容有其自身的许可条件--这是在页面底部非常小的链接:“cc-wiki with attribution required” 。请遵循这些链接,并遵守那里规定的条件。 - Daniel C. Sobral
2
那么,你的Scala书什么时候出版,我在哪里可以购买它呢 :) - wfbarksdale
1
一些 Stack Overflow 的回答价值甚至超过了三本不同书籍中关于该主题的三章内容。 - Ilya Smagin
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接