as-keyword 似乎是早期 Dotty 版本的一些神器;它没有在 Scala 3 中使用。当前有效的语法是:
given listOrdering[A](using ord: Ordering[A]): Ordering[List[A]] with
def compare(a: List[A], b: List[A]) =
Scala Book 给出了在 given-declarations 中使用 with 关键字的以下理由:
因为在声明给定别名时,通常在等号右侧定义特征或类的匿名实例,Scala 提供了一种简写语法来替换等号和别名的“新 ClassName”部分仅使用关键字 with 给出。
即
given foobar[X, Y, Z]: ClassName[X, Y, Z] = new ClassName[X, Y, Z]:
def doSomething(x: X, y: Y): Z = ???
变成
given foobar[X, Y, Z]: ClassName[X, Y, Z] with
def doSomething(x: X, y: Y): Z = ???
with 关键字的选择似乎并不特别重要:它只是一些已经保留的关键字,在这种情况下听起来或多或少自然。我想它应该听起来有点类似于自然语言短语,如
"... 给定整数上的幺半群结构 a • b = a * b 和 e = 1 ..."
with 的这种用法特定于 given 声明,并不适用于任何其他上下文。 language reference 表明with 关键字作为终止符号出现在StructuralInstance 生产规则的右侧,即这个句法结构不能分解成仍然具有with 的更小的组成部分关键字。
我相信理解塑造语法的力量比实际语法本身重要得多,所以我将改为描述它是如何从普通方法定义中产生的。
步骤 0:假设我们需要一些类型类的实例Foo
让我们假设我们已经识别出一些常见的模式,并将其命名为Foo。像这样的:
trait Foo[X]:
def bar: X
def foo(a: X, b: X): X
第 1 步:在我们需要的地方创建 Foo 的实例。
现在,假设我们有一些方法 f 需要 Foo[Int]...
def f[A](xs: List[A])(foo: Foo[A]): A = xs.foldLeft(foo.bar)(foo.foo)
...我们可以在每次需要时写下Foo 的实例:
f(List(List(1, 2), List(3, 4)))(new Foo[List[Int]] {
def foo(a: List[Int], b: List[Int]) = a ++ b
def bar: List[Int] = Nil
})
- 作用力:需要
Foo的实例
- 解决方案:在我们需要它们的地方准确定义
Foo 实例
第 2 步:方法
在每次调用f 时写下方法foo 和bar 很快就会变得非常无聊和重复,所以我们至少将其提取到一个方法中:
def listFoo[A]: Foo[List[A]] = new Foo[List[A]] {
def foo(a: List[A], b: List[A]): List[A] = a ++ b
def bar: List[A] = Nil
}
现在我们不必在每次需要调用f 时都重新定义foo 和bar;相反,我们可以简单地调用listFoo:
f(List(List(1, 2), List(3, 4)))(listFoo[Int])
- 作用力:我们不想重复写下
Foo的实现
- 解决方案:将实现提取到辅助方法中
第三步:using
在每个A 基本上只有一个规范Foo[A] 的情况下,显式传递诸如listFoo[Int] 之类的参数也很快变得令人厌烦,因此,我们将listFoo 声明为given,并且通过添加using 使f 的foo 参数隐含:
def f[A](xs: List[A])(using foo: Foo[A]): A = xs.foldLeft(foo.bar)(foo.foo)
given listFoo[A]: Foo[List[A]] = new Foo[List[A]] {
def foo(a: List[A], b: List[A]): List[A] = a ++ b
def bar: List[A] = Nil
}
现在我们不必每次调用f 时都调用listFoo,因为Foo 的实例是自动生成的:
f(List(List(1, 2), List(3, 4)))
- 作用力:反复提供明显的规范论据令人厌烦
- 解决方案:将它们设为隐式,让编译器自动找到正确的实例
第 4 步:去重类型声明
given listFoo[A]: Foo[List[A]] = new Foo[List[A]] { 看起来有点傻,因为我们必须指定 Foo[List[A]]-part 两次。相反,我们可以使用with:
given listFoo[A]: Foo[List[A]] with
def foo(a: List[A], b: List[A]): List[A] = a ++ b
def bar: List[A] = Nil
现在,类型中至少没有重复。
- 作用力:
given xyz: SomeTrait = new SomeTrait { } 语法嘈杂,包含重复部分
- 解决方案:使用
with-syntax,避免重复
第 5 步:不相关的名称
因为listFoo 是由编译器自动调用的,所以我们实际上并不需要这个名称,因为无论如何我们从不使用它。编译器可以自己生成一些合成名称:
given [A]: Foo[List[A]] with
def foo(a: List[A], b: List[A]): List[A] = a ++ b
def bar: List[A] = Nil
- 作用力:指定人类无论如何都不会使用的无关名称很烦人
- 解决方案:在不需要的地方省略
givens 的名称。
大家一起
在这个过程的最后,我们的例子变成了类似的东西
trait Foo[X]:
def foo(a: X, b: X): X
def bar: X
def f[A](xs: List[A])(using foo: Foo[A]): A = xs.foldLeft(foo.bar)(foo.foo)
given [A]: Foo[List[A]] with
def foo(a: List[A], b: List[A]): List[A] = a ++ b
def bar: List[A] = Nil
f(List(List(1, 2), List(3, 4)))
- 对于
Lists,foo/bar 方法没有重复定义。
- 无需显式传递
givens,编译器会为我们完成此操作。
-
given 定义中没有重复的类型
- 没有必要为不适合人类的方法发明不相关的名称。