给定实例
给定实例(或简单地称为“给定”)定义某些类型的“规范”值,这些值用于合成 上下文参数 的参数。示例
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)
此代码定义了一个具有两个给定实例的特征 Ord
。intOrd
为类型 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)
别名给定可以具有类型参数和上下文参数,就像任何其他给定一样,但它只能实现一个类型。
给定宏
给定别名可以具有 inline
和 transparent
修饰符。示例
transparent inline given mkAnnotations[A, T]: Annotations[A, T] = ${
// code producing a value of a subtype of Annotations
}
由于 mkAnnotations
是 transparent
,因此应用程序的类型是其右侧的类型,它可以是声明的结果类型 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` 和包含实例成员定义的模板主体。
- 别名实例包含一个类型,后面跟着 `=` 和一个右侧表达式。
- 抽象实例只包含类型,后面不跟任何东西。