Scala中的“上下文绑定”是什么?


115

Scala 2.8的新功能之一是上下文边界。什么是上下文绑定,它在哪里有用?

当然,我首先进行了搜索(并找到了this),但是找不到任何真正清晰详细的信息。



2
:这个绝佳的答案比较/对比背景下边界和界限观点stackoverflow.com/questions/4465948/...
阿伦Novstrup

Answers:


107

您找到这篇文章了吗?在数组改进的上下文中,它涵盖了新的上下文绑定功能。

通常,带有上下文绑定的类型参数的形式为[T: Bound]; 它与type T的隐式参数一起扩展为普通类型参数Bound[T]

考虑tabulate从将给定函数f应用于从0到给定长度的数字范围内的结果形成数组的方法。在Scala 2.7之前,表格可以编写如下:

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]。需要通过将a 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
}

作为一种简写形式,可以在type参数上使用上下文绑定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
}

145

Robert的答案涵盖了Context Bounds的技术细节。我将解释它们的含义。

在Scala中,“视图绑定”(A <% B)捕获了“可以被视为” <:的概念(而上限捕获了“是a”的概念)。上下文绑定(A : C)对类型说“有”。您可以阅读有关清单的示例,因为清单中的“ T具有Manifest”。您链接到about Ordered与的Ordering示例说明了差异。一个方法

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

表示该参数可以视为Ordered。与之比较

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

表示该参数具有关联的Ordering

在使用方面,建立约定花费了一段时间,但是上下文边界比视图边界更受青睐(视图边界现在已弃用)。一个建议是,当您需要将隐式定义从一个范围转移到另一个范围而不需要直接引用它时(最好是ClassManifest用于创建数组的情况),最好使用上下文绑定 。

考虑视图范围和上下文范围的另一种方法是,第一种方法从调用者的作用域转移隐式转换。第二种方法从调用者的作用域传输隐式对象。


2
对我而言,“具有”而不是“是”或“被视为”是我的主要见识-在其他任何解释中都没有看到。普通的英文版本的运算符/函数有些晦涩难懂,因此吸收起来会容易得多-谢谢!
DNA

1
@本灵斯(Ben Lings)你的意思是..... 对某种类型的 ... “有”吗?什么是类型
jhegedus 2014年

1
@jhegedus这是我的解析:“关于类型”表示A表示类型。短语“具有”通常在面向对象的设计中用于描述对象关系(例如,客户“具有”地址)。但是,这里的“具有”关系是类型之间的关系,而不是对象之间的关系。这是一个宽松的类比,因为“具有”关系不是OO设计中固有的或通用的。客户总是有一个地址,但是对于上下文绑定,一个A并不总是具有C。相反,上下文绑定指定必须隐式提供C [A]的实例。
jbyler '16

我已经学习了Scala一个月,这是我本月见过的最好的解释!谢谢@Ben!
Lifu Huang

@本灵斯(Ben Lings):谢谢,花了很长时间了解上下文绑定之后,您的回答非常有帮助。[ has a对我来说更有意义]
尚卡尔

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

这也可以通过上下文绑定(Context Bound)来表示,它借助于表示类型从type F到type的函数的类型别名来实现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是一种(*, *) => *。使用类型别名会部分地将第二个类型参数与type配合使用String,从而产生正确类型的类型构造函数,以用作上下文绑定。

有一种建议可以让您直接在Scala中表达部分应用的类型,而无需在特征内使用类型别名。然后,您可以编写:

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

您能否在f2的定义中解释#From的含义?我不确定在哪里构造F型(我说得对吗?)
Collin 2010年

1
这就是所谓的类型的投射,引用类型成员From的类型To[String]。我们没有为提供类型参数From,因此我们引用的是类型构造函数,而不是类型。这种类型的构造函数是正确的,可以用作上下文绑定- * -> *T通过要求type 的隐式参数来限制type参数To[String]#From[T]。展开类型别名,瞧,剩下的就是Function1[String, T]
retronym 2010年

应该是Function1 [T,String]吗?
ssanj 2011年

18

这是另一个括号。

正如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) }
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.