在Scala中,“context bound”是什么?

118

Scala 2.8的一个新特性是上下文界定(context bounds)。什么是上下文界定以及它有哪些用处?

当然,我先进行了搜索(例如在这里找到了一些信息),但是我没有找到任何真正清晰和详细的信息。


8
请查看此链接以了解所有类型的边界游览:https://gist.github.com/257758/47f06f2f3ca47702b3a86c76a5479d096cb8c7ec。 - Arjan Blokzijl
2
这个优秀的回答比较了上下文界限和视图界限:https://dev59.com/VW855IYBdhLWcg3wNxkM#4467012 - Aaron Novstrup
这是一个非常好的答案 https://dev59.com/14Lba4cB1Zd3GeqPd2ES#25250693 - samthebest
4个回答

156

罗伯特的答案涵盖了上下文界限的技术细节。我会给出我的解释。

在Scala中,视图界限(A <% B)捕获“可以被视为”的概念(而上限 <: 捕获“是一个”的概念)。上下文界限(A:C)表示一个类型“拥有”某个特性。您可以将关于标记的示例阅读为“T拥有一个Manifest”。您链接到的关于OrderedOrdering的示例说明了区别。一个方法

def example[T <% Ordered[T]](param: T)

说这个参数可以被看作是一个Ordered。与之比较

def example[T : Ordering](param: T)

该参数具有相关的 Ordering

在使用方面,花费了一些时间才确立了惯例,但是语境限定符比视图限定符更受欢迎 (现在已弃用视图限定符)。一个建议是:当您需要在不直接引用它的情况下从一个作用域传递一个隐式定义时(这对于用于创建数组的ClassManifest 绝对是这种情况),建议使用语境限定符。

另一种理解视图限定符和语境限定符的方式是:前者从调用方的范围传递隐式转换。后者从调用方的范围传递隐式对象。


2
“拥有”而不是“是”或“被视为”是对我最关键的洞见-在其他解释中没有看到过这一点。拥有一个简明易懂的英文版本,使得那些稍微有些晦涩的运算符/函数更容易吸收-谢谢! - DNA
1
@Ben Lings,您所说的“has a”关于类型是什么意思?“about a type”是指什么? - jhegedus
1
@jhegedus 这是我的解析结果:“关于类型”的意思是A指的是一个类型。短语“有一个”在面向对象设计中经常用来描述对象之间的关系(例如,客户“有一个”地址)。但是在这里,“有一个”关系是在类型之间而不是对象之间。这是一个宽泛的类比,因为“有一个”关系并不像在OO设计中那样固有或普遍;客户始终有地址,但是对于上下文限定,A并不总是有C。相反,上下文限定指定必须隐式提供C[A]的实例。 - jbyler
我已经学习Scala一个月了,这是我这个月看到的最好的解释!谢谢@Ben! - Lifu Huang
@Ben Lings:谢谢,花了这么长时间才理解什么是上下文绑定,你的回答非常有帮助。[has a 对我来说更有意义] - Shankar

112

您是否看到了这篇文章?它涵盖了新的上下文界限特性,以数组改进为背景。

通常,带有上下文界限的类型参数形式为[T: Bound];它被扩展为普通类型参数T和类型为Bound[T]的隐式参数。

考虑方法tabulate,它通过在从0到给定长度的数字范围上应用给定函数f的结果来形成一个数组。在Scala 2.7之前,可以将tabulate写成以下形式:

def tabulate[T](len: Int, f: Int => T) = {
    val xs = new Array[T](len)
    for (i <- 0 until len) xs(i) = f(i)
    xs
}

在Scala 2.8中,不再支持这种操作,因为需要运行时信息来创建正确的Array[T]表示。必须通过将ClassManifest[T]作为隐式参数传递给方法来提供此信息:
def tabulate[T](len: Int, f: Int => T)(implicit m: ClassManifest[T]) = {
    val xs = new Array[T](len)
    for (i <- 0 until len) xs(i) = f(i)
    xs
}

作为一种简写形式,可以在类型参数 T 上使用上下文绑定,即:
def tabulate[T: ClassManifest](len: Int, f: Int => T) = {
    val xs = new Array[T](len)
    for (i <- 0 until len) xs(i) = f(i)
    xs
}

39

(这是一个括号注释。先阅读和理解其他答案。)

上下文界限实际上是一般化的视图界限。

因此,考虑到以下使用视图界限表达的代码:

scala> implicit def int2str(i: Int): String = i.toString
int2str: (i: Int)String

scala> def f1[T <% String](t: T) = 0
f1: [T](t: T)(implicit evidence$1: (T) => String)Int

这也可以通过上下文边界来表达,借助于一个类型别名,表示从类型F到类型T的函数。

scala> trait To[T] { type From[F] = F => T }           
defined trait To

scala> def f2[T : To[String]#From](t: T) = 0       
f2: [T](t: T)(implicit evidence$1: (T) => java.lang.String)Int

scala> f2(1)
res1: Int = 0

一个上下文边界必须与 kind 为 * => * 的类型构造器一起使用。然而,类型构造器 Function1 的 kind 是 (*, *) => *。使用类型别名部分应用第二个类型参数,其类型为 String,产生了一个正确种类的类型构造器,可用作上下文边界。

有一个提案允许您在 Scala 中直接表达部分应用的类型,而无需在特质中使用类型别名。然后您可以编写:

def f3[T : [X](X => String)](t: T) = 0 

你能解释一下在f2的定义中#From的含义吗?我不确定F类型是在哪里构建的(我说得对吗?) - Collin
1
它被称为类型投影,引用了类型成员 To[String] 的类型成员 From。我们没有为 From 提供类型参数,因此我们引用的是类型构造函数,而不是类型本身。这个类型构造函数是正确的种类,可以用作上下文边界——* -> *。这通过要求一个类型为 To[String]#From[T] 的隐式参数来限制类型参数 T。扩展类型别名,你就得到了 Function1[String, T] - retronym
应该是 Function1[T, String] 吗? - ssanj

19

这是另一个括号注释。

正如Ben所指出的,上下文界定表示类型参数和类型类之间的“具有”约束。换句话说,它表示特定类型类的隐式值存在的约束。

在使用上下文界定时,通常需要表明该隐式值。例如,给定约束T : Ordering,通常需要满足约束的Ordering[T]实例。正如此处所示,可以通过使用implicitly方法或稍微更有帮助的context方法来访问隐式值:

def **[T : Numeric](xs: Iterable[T], ys: Iterable[T]) = 
   xs zip ys map { t => implicitly[Numeric[T]].times(t._1, t._2) }

或者
def **[T : Numeric](xs: Iterable[T], ys: Iterable[T]) =
   xs zip ys map { t => context[T]().times(t._1, t._2) }

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