什么是Scala上下文和视图范围?


267

用简单的方法,上下文和视图范围是什么,它们之间有什么区别?

一些易于遵循的示例也将很棒!

Answers:


477

我以为已经有人问过这个问题,但是如果这样,这个问题在“相关”栏中就不明显了。因此,这里是:

什么是视图绑定?

绑定视图是在Scala中引入,以便能够使用某种类型的机制A ,就好像它是一些类型B。典型的语法是这样的:

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

换句话说,A应该将其隐式转换为B可用,以便可以B在type对象上调用方法A。在标准库中(无论如何,在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参数化类型进行的初始化需要提供a ClassManifest

库中另一个非常常见的示例稍微复杂一些:

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

在这里,implicitly用于检索我们想要的隐式值,它是type之一Ordering[A],该类定义了method 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有什么用?

视图边界通常用于利用我的库模式的皮条客模式,在这种情况下,如果您想以某种方式返回原始类型,可以通过该方法向现有类“添加”方法。如果不需要以任何方式返回该类型,则不需要视图绑定。

视图绑定用法的经典示例是处理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

如果尝试在没有视图边界的情况下执行此操作,则a的返回类型String将为WrappedString(Scala 2.8),对于则类似Array

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

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

上下文界限有什么用?

上下文边界主要用于所谓的typeclass模式中,作为对Haskell类型类的引用。基本上,此模式通过使功能通过某种隐式适配器模式可用来实现继承的替代方法。

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

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库,它为Scala带来了Haskell的很多功能。我建议阅读类型类模式,以更熟悉使用它的所有方式。

编辑

感兴趣的相关问题:


9
非常感谢。我知道以前已经回答过这个问题,也许那时我还没有足够仔细地阅读过,但是您在这里的解释是我所见过的最清晰的解释。所以,再次感谢您。
chrsan 2010年

3
@chrsan我又添加了两个部分,详细介绍了每个部分在哪里使用。
Daniel C. Sobral 2010年

2
我认为这是一个很好的解释。如果可以的话,我想翻译成我的德语博客(dgronau.wordpress.com)。
兰代2010年

3
到目前为止,这是迄今为止我发现的关于该主题的最好,最全面的解释。确实非常感谢您!
fotNelton 2011年

2
太棒了,什么时候您的Scala书问世,我在哪里可以买到它:)
wfbarksdale
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.