使用子句
函数式编程倾向于将大多数依赖项表示为简单的函数参数化。这既简洁又强大,但有时会导致函数采用许多参数,其中相同的值在调用链中一次又一次地传递给许多函数。上下文参数可以提供帮助,因为它们使编译器能够合成重复的参数,而不是让程序员必须显式编写它们。
例如,对于先前定义的给定实例,可以将适用于任何存在排序的论点的max
函数定义如下
def max[T](x: T, y: T)(using ord: Ord[T]): T =
if ord.compare(x, y) < 0 then 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[T]
的上下文参数,仅将其作为推断论点传递给max
。参数的名称被省略。
通常,上下文参数可以定义为完整参数列表(p_1: T_1, ..., p_n: T_n)
或仅定义为类型序列T_1, ..., T_n
。using
子句中不支持变参参数。
类上下文参数
要使类上下文参数在类主体外部可见,可以通过添加val
或var
修饰符将其变为成员。
class GivenIntBox(using val usingParameter: Int):
def myInt = summon[Int]
val b = GivenIntBox(using 23)
import b.usingParameter
summon[Int] // 23
这比创建显式given
成员更可取,因为后者在类主体内部会产生歧义
class GivenIntBox2(using usingParameter: Int):
given givenMember: Int = usingParameter
def n = summon[Int] // ambiguous given instances: both usingParameter and givenMember match type Int
在GivenIntBox
外部,usingParameter
显示为在类中定义为given usingParameter: Int
,特别是必须按导入given
部分中所述进行导入。
val b = GivenIntBox(using 23)
// Works:
import b.given
summon[Int] // 23
usingParameter // 23
// Fails:
import b.*
summon[Int] // No given instance found
usingParameter // Not found
推断复杂论点
以下是具有类型为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)
。在此设置中,以下所有调用都格式良好,并且它们都规范化为最后一个
minimum(xs)
maximum(xs)(using descending)
maximum(xs)(using descending(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
然后,以下所有调用都是有效的(并且规范化为最后一个)
f(global)
f(global)(using ctx)
f(global)(using ctx)(using sym, kind)
但 f(global)(using sym, kind)
会引发类型错误。
召唤实例
Predef
中的 summon
方法返回特定类型的给定值。例如,Ord[List[Int]]
的给定实例由以下内容生成:
summon[Ord[List[Int]]] // reduces to listOrd(using intOrd)
summon
方法简单地定义为上下文参数上的(非扩展)恒等函数。
def summon[T](using x: T): x.type = x
语法
以下是参数和参数的新语法,从 Scala 3 的标准上下文无关语法 中的增量中看到。using
是一个软关键字,仅在参数或参数列表的开头处识别。它可以在其他任何地方用作普通标识符。
ClsParamClause ::= ... | UsingClsParamClause
DefParamClause ::= ... | UsingParamClause
UsingClsParamClause ::= ‘(’ ‘using’ (ClsParams | Types) ‘)’
UsingParamClause ::= ‘(’ ‘using’ (DefTermParams | Types) ‘)’
ParArgumentExprs ::= ... | ‘(’ ‘using’ ExprsInParens ‘)’