什么是Scala上下文和视图边界?

chr*_*san 261 scala implicits

简单来说,什么是上下文和视图边界,它们之间有什么区别?

一些易于理解的例子也很棒!

Dan*_*ral 470

我认为这已经被问到了,但是,如果是这样的话,问题在"相关"栏中并不明显.所以,这里是:

什么是视界?

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

def f[A <% B](a: A) = a.bMethod
Run Code Online (Sandbox Code Playgroud)

换句话说,A应该有一个隐式转换为Bavailable,以便可以B在类型的对象上调用方法A.标准库中最常见的视图边界用法(无论如何,在Scala 2.8.0之前)Ordered,如下所示:

def f[A <% Ordered[A]](a: A, b: A) = if (a < b) a else b
Run Code Online (Sandbox Code Playgroud)

因为可以转换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]
Run Code Online (Sandbox Code Playgroud)

这比视图绑定更令人困惑,因为它不能立即清楚如何使用它.Scala中常见的使用示例是:

def f[A : ClassManifest](n: Int) = new Array[A](n)
Run Code Online (Sandbox Code Playgroud)

由于与类型擦除和阵列的非擦除性质相关的神秘原因,Array参数化类型的初始化需要ClassManifest可用.

库中另一个非常常见的例子有点复杂:

def f[A : Ordering](a: A, b: A) = implicitly[Ordering[A]].compare(a, b)
Run Code Online (Sandbox Code Playgroud)

这里,implicitly用于检索我们想要的隐式值,类型之一Ordering[A],哪个类定义方法compare(a: A, b: A): Int.

我们将在下面看到另一种方法.

如何实现View Bounds和Context Bounds?

鉴于它们的定义,视图边界和上下文边界都是使用隐式参数实现的,这应该不足为奇.实际上,我展示的语法是真正发生的语法糖.见下文他们如何脱糖:

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)
Run Code Online (Sandbox Code Playgroud)

所以,很自然地,人们可以用完整的语法编写它们,这对于上下文边界特别有用:

def f[A](a: A, b: A)(implicit ord: Ordering[A]) = ord.compare(a, b)
Run Code Online (Sandbox Code Playgroud)

View Bounds用于什么?

视图边界主要用于利用pimp我的库模式,在某种情况下,想要以某种方式返回原始类型,通过该模式向现有类"添加"方法.如果您不需要以任何方式返回该类型,则不需要视图绑定.

视图绑定用法的经典示例是处理Ordered.请注意,Int是不是Ordered,例如,虽然有一个隐含的转换.之前给出的示例需要一个视图绑定,因为它返回未转换的类型:

def f[A <% Ordered[A]](a: A, b: A): A = if (a < b) a else b
Run Code Online (Sandbox Code Playgroud)

没有视图边界,此示例将不起作用.但是,如果我要返回另一个类型,那么我不再需要视图绑定了:

def f[A](a: Ordered[A], b: A): Boolean = a < b
Run Code Online (Sandbox Code Playgroud)

这里的转换(如果需要)在我传递参数之前发生f,所以f不需要知道它.

此外Ordered,从库中最常见的用法是处理StringArray,这是Java类,像他们Scala集合.例如:

def f[CC <% Traversable[_]](a: CC, b: CC): CC = if (a.size < b.size) a else b
Run Code Online (Sandbox Code Playgroud)

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

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

def f[A <% Ordered[A]](xs: A*): Seq[A] = xs.toSeq.sorted
Run Code Online (Sandbox Code Playgroud)

什么是Context Bounds用于?

上下文边界主要用于所谓的类型类模式,作为对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
Run Code Online (Sandbox Code Playgroud)

虽然你通常会看到这样写的:

def f[A](a: A, b: A)(implicit ord: Ordering[A]) = {
    import ord.mkOrderingOps
    if (a < b) a else b
}
Run Code Online (Sandbox Code Playgroud)

利用内部的一些隐式转换Ordering来实现传统的运算符样式.Scala 2.8中的另一个例子是Numeric:

def f[A : Numeric](a: A, b: A) = implicitly[Numeric[A]].plus(a, b)
Run Code Online (Sandbox Code Playgroud)

一个更复杂的例子是新的集合使用CanBuildFrom,但已经有很长的答案,所以我会在这里避免它.并且,如前所述,有一些ClassManifest用法,这是初始化没有具体类型的新数组所必需的.

与类型模式绑定的上下文更可能由您自己的类使用,因为它们可以分离关注点,而通过良好的设计可以在您自己的代码中避免视图边界(它主要用于绕过其他人的设计) ).

尽管已经有很长一段时间了,但是在2010年,上下文界限的使用已经真正起步,现在在Scala最重要的大多数库和框架中都有一定程度的发现.然而,它最常用的例子是Scalaz库,它为Halaell带来了很多Scala的强大功能.我建议阅读类型类模式,以便更好地了解它的所有使用方式.

编辑

相关问题:

  • 非常感谢.我知道这已经得到了回答,也许我当时没有仔细阅读,但你在这里的解释是我见过的最清楚的.所以,再次感谢你. (8认同)
  • @chrsan我添加了两个部分,详细介绍了每个部分使用的部分. (3认同)
  • 到目前为止,这是迄今为止我发现的最佳和最全面的解释.非常感谢你! (3认同)
  • 我认为这是一个很好的解释。如果可以的话,我想翻译成我的德语博客(dgronau.wordpress.com)。 (2认同)
  • 太好了,您的Scala书何时出版,我在哪里可以买到:) (2认同)