Link Search Menu Expand Document

Using 子句

函数式编程倾向于将大多数依赖关系表示为简单的函数参数化。这是干净而强大的, 但有时会让函数接受很多参数,在长调用链中一次又一次传递相同的值给很多函数。 上下文参数在这里很有用,它们能让编译器合成重复的参数,而不要求程序员显式编写它们。

例如,与前面说明过的 given 实例协同,适用于任何可排序参数的 max 函数可以这样定义:

def max[T](x: T, y: T)(using ord: Ord[T]): T =
   if (ord.compare(x, y) < 0) y else x

这里 ord 是由 using 子句引入的上下文参数max 函数可以这样应用:

max(2, 3)(using intOrd)

(using intOrd) 部分把 intOrd 作为形参 ord 的实参传递。但是上下文参数的关键点在于调用时参数可以省略(通常是这样)。 因此,下面的应用同样有效:

max(2, 3)
max(List(1, 2, 3), Nil)

匿名上下文参数

在很多情况下,上下文参数的名称完全不需要显式写出,因为它只用于合成其他上下文参数的实参。 这种情况下,可以避免写出参数名,只需要写出它的类型。例如:

def maximum[T](xs: List[T])(using Ord[T]): T =
   xs.reduceLeft(max)

maximum 接受一个 Ord 类型的上下文参数,只把它作为推断出的参数传递给 max。 该参数的名称被忽略。

通常来说,上下文参数可以定义为一个完整的参数列表 (p_1: T_1, ..., p_n: T_n), 也可以定义为一串类型序列 T_1, ..., T_n。Using 子句中不支持可变参数。

推断复杂参数

下面是另外两个具有 Ord[T] 类型的上下文参数的方法:

def descending[T](using asc: Ord[T]): Ord[T] = new Ord[T] {
   def compare(x: T, y: T) = asc.compare(y, x)
}

def minimum[T](xs: List[T])(using Ord[T]) =
   maximum(xs)(using descending)

minimum 方法的右侧将 descending 作为显式参数传递给 maximum(xs)。 With this setup,以下调用都是格式良好的,并都会被 normalize 到最后一种调用形式:

minimum(xs)
maximum(xs)(using descending)
maximum(xs)(using descending(using listOrd))
maximum(xs)(using descending(using listOrd(using intOrd)))

多个 using 子句

一个定义中可以有多个 using 子句,using 子句可以和普通参数子句自由混合。例如:

def f(u: Universe)(using ctx: u.Context)(using s: ctx.Symbol, k: ctx.Kind) = ...

多个 using 子句在应用时从左向右批评。例如:

object global extends Universe { type Context = ... }
given ctx : global.Context with { type Symbol = ...; type Kind = ... }
given sym : ctx.Symbol
given kind: ctx.Kind

那么以下调用都是有效的(并 normalize 到最后一种调用形式)。

f(global)
f(global)(using ctx)
f(global)(using ctx)(using sym, kind)

但是 f(global)(using sym, kind) 会产生一个类型错误。

召唤实例

Predef 中的方法 summon 返回指定类型的 given 值。例如,Ord[List[Int]] 类型的 given 实例可以这样生成:

summon[Ord[List[Int]]]  // reduces to listOrd(using intOrd)

summon 方法简单地定义为上下文参数上的 identity 函数(non-widening)。

def summon[T](using x: T): x.type = x

语法

下面是 Scala 3 标准上下文无关语法中形参和实参的新语法。using 是一个软关键字, 只会在形参或实参列表的开头被识别。它可以在其他地方被用作普通标识符。

ClsParamClause      ::=  ... | UsingClsParamClause
DefParamClauses     ::=  ... | UsingParamClause
UsingClsParamClause ::=  ‘(’ ‘using’ (ClsParams | Types) ‘)’
UsingParamClause    ::=  ‘(’ ‘using’ (DefParams | Types) ‘)’
ParArgumentExprs    ::=  ... | ‘(’ ‘using’ ExprsInParens ‘)’