Scala3 "as" 和 "with" 关键字与 "given" 一起使用

Scala3 "as" and "with" keywords used with "given"

目前正在学习 Scala 3 隐式,但我很难理解 aswith 关键字在如下定义中的作用:

given listOrdering[A](using ord: Ordering[A]) as Ordering[List[A]] with
 ​def compare(a: List[A], b: List[A]) = ...

我试着用谷歌搜索,但没有真正找到任何好的解释。我已经查看了 Scala 3 参考指南,但我唯一发现 as 的是它是一个“软修饰符”,但这并不能真正帮助我理解它的作用......我'我猜测上面代码中的 as 以某种方式用于澄清 listOrdering[A] 是一个 Ordering[List[A]](就像正在进行某种类型的打字或类型转换?),但这会很棒去寻找它背后的真正含义。

至于with,我只在Scala 2中使用它来继承多个特征(class A extends B with C with D),但在上面的代码中,它似乎以不同的方式使用.. .

非常感谢任何解释或指出我在何处查看文档的正确方向!

此外,如果上面的代码是用 Scala 2 编写的,会是什么样子?也许这会帮助我弄清楚发生了什么......

as-关键字似乎是早期 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 声明中使用 with 关键字的以下基本原理:

Because it is common to define an anonymous instance of a trait or class to the right of the equals sign when declaring an alias given, Scala offers a shorthand syntax that replaces the equals sign and the "new ClassName" portion of the alias given with just the keyword 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 关键字的选择似乎并不特别重要:它只是一些已经保留的关键字,在这种情况下听起来或多或少很自然。我猜它听起来应该与

这样的自然语言短语有些相似

"... given a monoid structure on integers with a • b = a * b and 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 时写下方法 foobar 很快就会变得非常无聊和重复,所以让我们至少将其提取到一个方法中:

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时都重新定义foobar;相反,我们可以简单地调用 listFoo:

f(List(List(1, 2), List(3, 4)))(listFoo[Int])
  • 执行力:我们不想重复Foo的实现
  • 解决方案:将实现提取到辅助方法中

第 3 步:using

在每个 A 基本上只有一个规范 Foo[A] 的情况下,显式传递诸如 listFoo[Int] 之类的参数也很快变得令人厌烦,因此我们声明 listFoo 成为 given,并通过添加 using:

隐含 ffoo 参数
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]] 部分两次。相反,我们可以使用 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-语法,避免重复

第 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
  • 作用力:指定不被人类使用的不相关名称很烦人
  • 解决方案:省略不需要的 given 的名称。

一起

在这个过程的最后,我们的例子变成了这样的东西

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)))
  • List 没有重复定义 foo/bar 方法。
  • 无需显式传递 given,编译器会为我们完成。
  • given 定义中没有重复的类型
  • 没有必要为不适合人类的方法发明不相关的名称。