在 GitHub 上编辑此页面

给定实例

给定实例(或简单地称为“给定”)定义某些类型的“规范”值,这些值用于合成 上下文参数 的参数。示例

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

given intOrd: Ord[Int] with
  def compare(x: Int, y: Int) =
    if x < y then -1 else if x > y then +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 then fst else compare(xs1, ys1)

此代码定义了一个具有两个给定实例的特征 OrdintOrd 为类型 Ord[Int] 定义一个给定,而 listOrd[T]Ord[List[T]] 定义给定,适用于所有类型 T,这些类型本身带有 Ord[T] 的给定实例。listOrd 中的 using 子句定义了一个条件:必须存在类型为 Ord[T] 的给定才能存在类型为 Ord[List[T]] 的给定。此类条件由编译器扩展为 上下文参数

匿名给定

可以省略给定名称。因此,上一部分的定义也可以这样表示

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

如果缺少给定名称,编译器将从实现的类型中合成一个名称。

注意:编译器合成的名称应具有可读性和简洁性。例如,上面的两个实例将获得名称

given_Ord_Int
given_Ord_List

合成名称的精确规则请参见此处。这些规则并不能保证“过于相似”的类型的给定实例之间不存在名称冲突。为了避免冲突,可以使用命名实例。

注意:为了确保稳健的二进制兼容性,公开的库应优先使用命名实例。

别名给定

可以使用别名来定义一个等于某个表达式的给定实例。示例

given global: ExecutionContext = ForkJoinPool()

这将创建一个类型为 ExecutionContext 的给定 global,它解析为右侧的 ForkJoinPool()。首次访问 global 时,将创建一个新的 ForkJoinPool,然后将其返回以用于此访问和所有后续对 global 的访问。此操作是线程安全的。

别名给定也可以是匿名的,例如

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

别名给定可以具有类型参数和上下文参数,就像任何其他给定一样,但它只能实现一个类型。

给定宏

给定别名可以具有 inlinetransparent 修饰符。示例

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

由于 mkAnnotationstransparent,因此应用程序的类型是其右侧的类型,它可以是声明的结果类型 Annotations[A, T] 的适当子类型。

给定实例可以具有 inline 但不能具有 transparent 修饰符,因为它们的类型已经从签名中得知。示例

trait Show[T] {
  inline def show(x: T): String
}

inline given Show[Foo] with {
  /*transparent*/ inline def show(x: Foo): String = ${ ... }
}

def app =
  // inlines `show` method call and removes the call to `given Show[Foo]`
  summon[Show[Foo]].show(foo)

请注意,给定实例中的内联方法可能是 transparent

给定实例的内联不会内联/复制给定的实现,它只会内联该实例的实例化。这用于帮助消除内联后未使用的给定实例的死代码。

模式绑定给定实例

给定实例也可以出现在模式中。示例

for given Context <- applicationContexts do

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

在上面的第一个片段中,通过枚举 `applicationContexts`,为类 `Context` 建立匿名给定实例。在第二个片段中,通过与 `pair` 选择器的上半部分匹配,建立了一个名为 `ctx` 的给定 `Context` 实例。

在每种情况下,模式绑定给定实例都由 `given` 和类型 `T` 组成。该模式与类型归属模式 `_: T` 完全匹配相同的选择器。

否定给定

Scala 2 在歧义方面有些令人费解的行为,已被用来在隐式解析中实现“否定”搜索的类似物,其中如果其他查询 Q2 成功,则查询 Q1 失败,如果 Q2 失败,则 Q1 成功。有了新的清理行为,这些技术不再起作用。但现在新的特殊类型 scala.util.NotGiven 直接实现了否定。

对于任何查询类型 `Q`,NotGiven[Q] 仅在对 `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]()
  assert(summon[Foo[Int]].value) // fooTagged is found
  assert(!summon[Foo[String]].value) // fooNotTagged is found

给定实例初始化

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

语法

以下是给定实例的语法

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

给定实例以保留字 `given` 和可选的签名开头。签名为实例定义名称和/或参数。后面跟着 `:`. 有三种给定实例

  • 结构实例包含一个或多个类型或构造函数应用程序,后面跟着 `with` 和包含实例成员定义的模板主体。
  • 别名实例包含一个类型,后面跟着 `=` 和一个右侧表达式。
  • 抽象实例只包含类型,后面不跟任何东西。