在 GitHub 上编辑此页面

使用子句

函数式编程倾向于将大多数依赖项表示为简单的函数参数化。这既简洁又强大,但有时会导致函数采用许多参数,其中相同的值在调用链中一次又一次地传递给许多函数。上下文参数可以提供帮助,因为它们使编译器能够合成重复的参数,而不是让程序员必须显式编写它们。

例如,对于先前定义的给定实例,可以将适用于任何存在排序的论点的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_nusing子句中不支持变参参数。

类上下文参数

要使类上下文参数在类主体外部可见,可以通过添加valvar修饰符将其变为成员。

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 ‘)’