简单来说,什么是上下文和视图边界,它们之间有什么区别?
一些易于理解的例子也很棒!
我认为这已经被问过了,但是,如果是这样,那么问题在“相关”栏中并不明显。所以,这里是:
视图绑定是 Scala 中引入的一种机制,可以像使用某种类型A
一样使用某种类型B
。典型的语法是这样的:
def f[A <% B](a: A) = a.bMethod
换句话说,A
应该有一个到可用的隐式转换B
,以便可以调用B
类型对象的方法A
。标准库中视图边界的最常见用法(无论如何,在 Scala 2.8.0 之前)是 with 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)
视图边界主要用于利用pimp my library模式,在您希望以某种方式返回原始类型的情况下,通过该模式将方法“添加”到现有类。如果您不需要以任何方式返回该类型,那么您不需要绑定视图。
视图绑定用法的经典示例是处理Ordered
。请注意Int
is not 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
,库中最常见的用法是处理String
和Array
,它们是 Java 类,就像 Scala 集合一样。例如:
def f[CC <% Traversable[_]](a: CC, b: CC): CC = if (a.size < b.size) a else b
如果尝试在没有视图边界的情况下执行此操作,则 a 的返回类型String
将是 a WrappedString
(Scala 2.8),对于Array
.
即使该类型仅用作返回类型的类型参数,也会发生同样的事情:
def f[A <% Ordered[A]](xs: A*): Seq[A] = xs.toSeq.sorted
上下文边界主要用于所谓的类型类模式,作为对 Haskell 类型类的引用。基本上,这种模式通过一种隐式适配器模式使功能可用,实现了继承的替代方案。
经典的例子是 Scala 2.8's 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
用法,它是在没有具体类型的情况下初始化新数组所必需的。
与 typeclass 模式绑定的上下文更有可能被您自己的类使用,因为它们可以分离关注点,而通过良好的设计可以在您自己的代码中避免视图边界(它主要用于绕过其他人的设计)。
尽管已经有很长一段时间了,但上下文边界的使用在 2010 年才真正起飞,现在在 Scala 的大多数最重要的库和框架中都在某种程度上发现了它。然而,它使用最极端的例子是 Scalaz 库,它为 Scala 带来了 Haskell 的强大功能。我建议阅读类型类模式,以更了解它的所有使用方式。
编辑
感兴趣的相关问题: