Link Search Menu Expand Document

Given 实例

Given 实例(或者简单称为 given)定义了某些类型的“经典”值,这些值用于为上下文参数合成实际参数。 例如:

trait Ord[T] {
   def compare(x: T, y: T): Int
   extension (x: T) def < (y: T) = compare(x, y) < 0
   extension (x: T) def > (y: T) = compare(x, y) > 0
}

given intOrd: Ord[Int] with {
   def compare(x: Int, y: Int) =
      if (x < y) -1 else if (x > y) +1 else 0
}

given listOrd[T](using ord: Ord[T]): Ord[List[T]] with {

   def compare(xs: List[T], ys: List[T]): Int = (xs, ys) match {
      case (Nil, Nil) => 0
      case (Nil, _) => -1
      case (_, Nil) => +1
      case (x :: xs1, y :: ys1) => {
         val fst = ord.compare(x, y)
         if (fst != 0) fst else compare(xs1, ys1)
      }
   }
}

这段代码定义了一个具有两个 given 实例的 trait OrdintOrd 为类型 Ord[Int] 定义了 given 实例, 而 listOrd[T] 对任意类型 T 为类型 Ord[List[T]] 定义了 Given 实例。listOrdusing 子句定义了一个条件: 只有在类型 Ord[T] 的 given 实例存在时才存在 Ord[List[T]] 类型的 given 实例。 编译器将这些条件扩展为上下文参数

匿名 Given

Given 的名称可以省略。所以最后一段的定义也可以这样表示:

given Ord[Int] with {
   ...
}

given [T](using Ord[T]): Ord[List[T]] with {
   ...
}

如果 given 缺少名称,编译器就会从实现的类型合成名称。

注意,编译器选择的合成名称简介且具有可读性。例如,上面两个实例会获得这样的名称:

given_Ord_Int
given_Ord_List_T

这里可以找到合成名称的精准规则。 这些规则不能保证“过于相似”的 given 实例之间不存在名称冲突。可以使用命名实例避免冲突。

注意,为了保证健壮的二进制兼容性,公共库应该更倾向使用命名实例。

别名 Given

别名可以用于定义与某个表达式相等的 given 实例。例如:

given global: ExecutionContext = ForkJoinPool()

这会创建一个 ExecutionContext 类型的 given global,它被解析到右侧的 ForkJoinPool()。 第一次访问 global 时会创建一个新的 ForkJoinPool 并返回,之后所有对 global 的访问都会返回这个 ForkJoinPool。 这个操作是线程安全的。

别名 given 也可以是匿名的,例如:

given Position = enclosingTree.position
given (using config: Config): Factory = MemoizingFactory(config)

别名 given 可以像其他 given 一样拥有类型参数和上下文参数,但它们只能为一个类型实现。

Given 宏

别名 Given 可以又 inlinetransparent 修饰符。例如:

transparent inline given mkAnnotations[A, T]: Annotations[A, T] = ${
  // code producing a value of a subtype of Annotations
}

因为 mkAnnotationstransparent 的,所以一个应用的类型是其右侧的类型, 它可以是声明的结果 Annotations[A, T] 类型的恰当子类型。

Pattern-Bound Given Instances

Given 实例也可以在模式中出现。例如:

for (given Context <- applicationContexts) {
   ...
}

pair match {
   case (ctx @ given Context, y) => ...
}

在上面第一个片段中,通过在 applicationContexts 上枚举创建类 Context 的匿名 given 实例。 第二个片段中通过匹配 pair 选择器的前半部分来创建一个名为 ctxContext 类型 given 实例。

在所有情况下,模式绑定的 given 实例都由 given 和类型 T 组成。 The pattern matches exactly the same selectors as the type ascription pattern _: T.

否定 Given

Scala 2 中在模糊性上的一些令人费解的行为被用来实现隐式解析中的“否定”搜索,如果查询 Q1 成功则查询 Q2 失败,如果查询 Q2 成功则查询 Q1 失败。 这些技术随着对隐式新的清理不再工作,但现在新的特殊类型 scala.util.NotGiven 直接实现否定。

对于任意查询类型 Q,当且仅当对 Q 的隐式搜索失败时,NotGiven[Q] 才会成功。例如:

import scala.util.NotGiven

trait Tagged[A]

case class Foo[A](value: Boolean)
object Foo {
   given fooTagged[A](using Tagged[A]): Foo[A] = Foo(true)
   given fooNotTagged[A](using NotGiven[Tagged[A]]): Foo[A] = Foo(false)
}

@main def test(): Unit = {
   given Tagged[Int] with {}
   assert(summon[Foo[Int]].value) // fooTagged is found
   assert(!summon[Foo[String]].value) // fooNotTagged is found
}

Given 实例初始化

没有类型或上下文参数的 given 实例在第一次访问的时候按需初始化。如果 given 有类型或者上下文参数, 则为每个引用创建一个新实例。

语法

这里是 given 实例的语法:

TmplDef             ::=  ...
                     |   ‘given’ GivenDef
GivenDef            ::=  [GivenSig] StructuralInstance
                     |   [GivenSig] AnnotType ‘=’ Expr
                     |   [GivenSig] AnnotType
GivenSig            ::=  [id] [DefTypeParamClause] {UsingParamClause} ‘:’
StructuralInstance  ::=  ConstrApp {‘with’ ConstrApp} ‘with’ TemplateBody

Given 实例以保留字 given 和一个可选的签名开始。签名定义实例的名称和参数。然后是 :、 Given 实例一共有三种:

  • 结构实例(Structural Instance)包含一个或多个类型或构造器应用,其后紧跟着 with和一个包含实例成员定义的模板体、
  • 别名实例(Alias Instance)包含一个类型,其后紧跟着 = 和一个右侧表达式。
  • 抽象实例(Abstract Instance)只包含类型,其后不跟随任何内容。