什么是 Scala 上下文和视图边界? [英] What are Scala context and view bounds?

查看:33
本文介绍了什么是 Scala 上下文和视图边界?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

解决方案

我以为这已经有人问过了,但是,如果是这样,那么问题在相关"栏中并不明显.所以,这里是:

什么是视图边界?

A view bound 是 Scala 中引入的一种机制,用于启用某种类型的 A 就好像它是某种类型的 B.典型的语法是这样的:

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

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

def f[A <% Ordered[A]](a: A, b: A) = if (a 

因为可以将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)//其中 g 需要 B[A] 类型的隐式值

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

def f[A : ClassManifest](n: Int) = new Array[A](n)

参数化类型的 Array 初始化需要一个 ClassManifest 可用,因为与类型擦除和数组的非擦除性质相关的神秘原因.

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

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

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

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

如何实现视图边界和上下文边界?

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

def f[A <% B](a: A) = a.bMethoddef f[A](a: A)(隐式 ev: A => B) = a.bMethoddef g[A : B](a: A) = h(a)def g[A](a: A)(隐式 ev: B[A]) = h(a)

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

def f[A](a: A, b: A)(隐式 ord: Ordering[A]) = ord.compare(a, b)

视图边界的用途是什么?

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

视图绑定使用的经典示例是处理Ordered.请注意,例如,Int 不是 Ordered,尽管存在隐式转换.前面给出的示例需要一个视图绑定,因为它返回未转换的类型:

def f[A <% Ordered[A]](a: A, b: A): A = if (a 

如果没有视图边界,此示例将无法工作.但是,如果我要返回另一种类型,那么我就不再需要视图绑定:

def f[A](a: Ordered[A], b: A): Boolean = a <乙

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

除了 Ordered,库中最常见的用法是处理 StringArray,它们是 Java 类,就像它们是 Scala 集合.例如:

def f[CC <% Traversable[_]](a: CC, b: CC): CC = if (a.size 

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

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

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

上下文边界有什么用?

上下文边界主要用于众所周知的类型类模式,作为对 Haskell 类型类的引用.基本上,此模式通过一种隐式适配器模式使功能可用,从而实现了继承的替代方案.

经典示例是 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)(隐式 ord: Ordering[A]) = {导入 ord.mkOrderingOps如果 (a < b) a else b}

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

def f[A : Numeric](a: A, b: A) = 隐式[Numeric[A]].plus(a, b)

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

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

虽然这已经有很长一段时间了,但上下文边界的使用在 2010 年才真正开始流行,现在在 Scala 的大多数最重要的库和框架中都在某种程度上找到了.然而,其使用最极端的例子是 Scalaz 库,它为 Scala 带来了 Haskell 的许多强大功能.我建议阅读类型类模式以更熟悉它的所有使用方式.

编辑

感兴趣的相关问题:

In a simple way, what are context and view bounds and what is the difference between them?

Some easy-to-follow examples would be great too!

解决方案

I thought this was asked already, but, if so, the question isn't apparent in the "related" bar. So, here it is:

What is a View Bound?

A view bound was a mechanism introduced in Scala to enable the use of some type A as if it were some type B. The typical syntax is this:

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

In other words, A should have an implicit conversion to B available, so that one can call B methods on an object of type A. The most common usage of view bounds in the standard library (before Scala 2.8.0, anyway), is with Ordered, like this:

def f[A <% Ordered[A]](a: A, b: A) = if (a < b) a else b

Because one can convert A into an Ordered[A], and because Ordered[A] defines the method <(other: A): Boolean, I can use the expression a < b.

Please be aware that view bounds are deprecated, you should avoid them.

What is a Context Bound?

Context bounds were introduced in Scala 2.8.0, and are typically used with the so-called type class pattern, a pattern of code that emulates the functionality provided by Haskell type classes, though in a more verbose manner.

While a view bound can be used with simple types (for example, A <% String), a context bound requires a parameterized type, such as Ordered[A] above, but unlike String.

A context bound describes an implicit value, instead of view bound's implicit conversion. It is used to declare that for some type A, there is an implicit value of type B[A] available. The syntax goes like this:

def f[A : B](a: A) = g(a) // where g requires an implicit value of type B[A]

This is more confusing than the view bound because it is not immediately clear how to use it. The common example of usage in Scala is this:

def f[A : ClassManifest](n: Int) = new Array[A](n)

An Array initialization on a parameterized type requires a ClassManifest to be available, for arcane reasons related to type erasure and the non-erasure nature of arrays.

Another very common example in the library is a bit more complex:

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

Here, implicitly is used to retrive the implicit value we want, one of type Ordering[A], which class defines the method compare(a: A, b: A): Int.

We'll see another way of doing this below.

How are View Bounds and Context Bounds implemented?

It shouldn't be surprising that both view bounds and context bounds are implemented with implicit parameters, given their definition. Actually, the syntax I showed are syntactic sugars for what really happens. See below how they de-sugar:

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)

So, naturally, one can write them in their full syntax, which is specially useful for context bounds:

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

What are View Bounds used for?

View bounds are used mostly to take advantage of the pimp my library pattern, through which one "adds" methods to an existing class, in situations where you want to return the original type somehow. If you do not need to return that type in any way, then you do not need a view bound.

The classic example of view bound usage is handling Ordered. Note that Int is not Ordered, for example, though there is an implicit conversion. The example previously given needs a view bound because it returns the non-converted type:

def f[A <% Ordered[A]](a: A, b: A): A = if (a < b) a else b

This example won't work without view bounds. However, if I were to return another type, then I don't need a view bound anymore:

def f[A](a: Ordered[A], b: A): Boolean = a < b

The conversion here (if needed) happens before I pass the parameter to f, so f doesn't need to know about it.

Besides Ordered, the most common usage from the library is handling String and Array, which are Java classes, like they were Scala collections. For example:

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

If one tried to do this without view bounds, the return type of a String would be a WrappedString (Scala 2.8), and similarly for Array.

The same thing happens even if the type is only used as a type parameter of the return type:

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

What are Context Bounds used for?

Context bounds are mainly used in what has become known as typeclass pattern, as a reference to Haskell's type classes. Basically, this pattern implements an alternative to inheritance by making functionality available through a sort of implicit adapter pattern.

The classic example is Scala 2.8's Ordering, which replaced Ordered throughout Scala's library. The usage is:

def f[A : Ordering](a: A, b: A) = if (implicitly[Ordering[A]].lt(a, b)) a else b

Though you'll usually see that written like this:

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

Which take advantage of some implicit conversions inside Ordering that enable the traditional operator style. Another example in Scala 2.8 is the Numeric:

def f[A : Numeric](a: A, b: A) = implicitly[Numeric[A]].plus(a, b)

A more complex example is the new collection usage of CanBuildFrom, but there's already a very long answer about that, so I'll avoid it here. And, as mentioned before, there's the ClassManifest usage, which is required to initialize new arrays without concrete types.

The context bound with the typeclass pattern is much more likely to be used by your own classes, as they enable separation of concerns, whereas view bounds can be avoided in your own code by good design (it is used mostly to get around someone else's design).

Though it has been possible for a long time, the use of context bounds has really taken off in 2010, and is now found to some degree in most of Scala's most important libraries and frameworks. The most extreme example of its usage, though, is the Scalaz library, which brings a lot of the power of Haskell to Scala. I recommend reading up on typeclass patterns to get more acquainted with all the ways in which it can be used.

EDIT

Related questions of interest:

这篇关于什么是 Scala 上下文和视图边界?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆