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

gre*_*Tea 5 syntax scala implicit scala-3 given

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

\n
given listOrdering[A](using ord: Ordering[A]) as Ordering[List[A]] with\n \xe2\x80\x8bdef compare(a: List[A], b: List[A]) = ...\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n

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

\n

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

\n

And*_*kin 11

-关键字as似乎是早期 Dotty 版本中的一些工件;Scala 3 中未使用它。当前有效的语法是:

\n
given listOrdering[A](using ord: Ordering[A]): Ordering[List[A]] with\n \xe2\x80\x8b def compare(a: List[A], b: List[A]) = ???\n
Run Code Online (Sandbox Code Playgroud)\n

Scala书中对于在声明中使用with关键字给出了以下理由given

\n
\n

由于在声明给定的别名时,通常会在等号右侧定义特征或类的匿名实例,因此 Scala 提供了一种速记语法,可以将等号和给出的别名的“new ClassName”部分替换为关键字with.

\n
\n

IE

\n
given foobar[X, Y, Z]: ClassName[X, Y, Z] = new ClassName[X, Y, Z]:\n  def doSomething(x: X, y: Y): Z = ???\n
Run Code Online (Sandbox Code Playgroud)\n

变成

\n
given foobar[X, Y, Z]: ClassName[X, Y, Z] with\n  def doSomething(x: X, y: Y): Z = ???\n
Run Code Online (Sandbox Code Playgroud)\n

关键字的选择with似乎并不特别重要:它只是一些已经保留的关键字,并且在这种情况下听起来或多或少是自然的。我想它听起来应该有点类似于自然语言短语,比如

\n
\n

“...给定整数上的幺半群结构,并带有 a \xe2\x80\xa2 b = a * be = 1...”

\n
\n

的这种用法with特定于given-声明,并且不能推广到任何其他上下文。语言参考显示 -with关键字作为终止符号出现在产生式规则的右侧StructuralInstance,即该句法构造不能分解为仍具有该with关键字的更小的组成部分。

\n
\n

我相信理解塑造语法的力量比实际语法本身重要得多,所以我将描述它是如何从普通方法定义中产生的。

\n

第 0 步:假设我们需要某个类型类的实例Foo

\n

让我们首先假设我们已经识别了一些常见的模式,并将其命名为Foo。像这样的东西:

\n
trait Foo[X]:\n  def bar: X\n  def foo(a: X, b: X): X\n
Run Code Online (Sandbox Code Playgroud)\n

第 1 步:Foo在我们需要的地方创建实例。

\n

现在,假设我们有一些方法f需要Foo[Int]......

\n
def f[A](xs: List[A])(foo: Foo[A]): A = xs.foldLeft(foo.bar)(foo.foo)\n
Run Code Online (Sandbox Code Playgroud)\n

Foo...我们可以在每次需要时写下一个实例:

\n
f(List(List(1, 2), List(3, 4)))(new Foo[List[Int]] {\n  def foo(a: List[Int], b: List[Int]) = a ++ b\n  def bar: List[Int] = Nil\n})\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  • 作用力:需要实例Foo
  • \n
  • Foo解决方案:在我们需要的地方准确定义实例
  • \n
\n

第 2 步:方法

\n

写下方法foobar在每次调用时f很快就会变得非常无聊和重复,所以让我们至少将其提取到一个方法中:

\n
def listFoo[A]: Foo[List[A]] = new Foo[List[A]] {\n  def foo(a: List[A], b: List[A]): List[A] = a ++ b\n  def bar: List[A] = Nil\n}\n
Run Code Online (Sandbox Code Playgroud)\n

现在我们不必foo每次bar需要调用时都重新定义f; 相反,我们可以简单地调用listFoo

\n
f(List(List(1, 2), List(3, 4)))(listFoo[Int])\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  • 作用力:我们不想Foo重复地写下实现
  • \n
  • 解决方案:将实现提取到辅助方法中
  • \n
\n

步骤3:using

\n

Foo[A]在每个基本上只有一个规范的情况下A,显式传递参数listFoo[Int]很快也会变得令人厌烦,因此,我们声明listFoo为 a given,并通过添加使foo- 参数隐式:fusing

\n
def f[A](xs: List[A])(using foo: Foo[A]): A = xs.foldLeft(foo.bar)(foo.foo)\n\ngiven listFoo[A]: Foo[List[A]] = new Foo[List[A]] {\n  def foo(a: List[A], b: List[A]): List[A] = a ++ b\n  def bar: List[A] = Nil\n}\n
Run Code Online (Sandbox Code Playgroud)\n

现在我们不必listFoo每次调用时都调用f,因为 的实例Foo是自动生成的:

\n
f(List(List(1, 2), List(3, 4)))\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  • 作用力:反复提供明显的规范论点很烦人
  • \n
  • 解决方案:使它们隐式,让编译器自动找到正确的实例
  • \n
\n

第 4 步:删除重复的类型声明

\n

看起来given listFoo[A]: Foo[List[A]] = new Foo[List[A]] {有点傻,因为我们必须指定Foo[List[A]]-part 两次。相反,我们可以使用with

\n
\ngiven listFoo[A]: Foo[List[A]] with\n  def foo(a: List[A], b: List[A]): List[A] = a ++ b\n  def bar: List[A] = Nil\n
Run Code Online (Sandbox Code Playgroud)\n

现在,至少类型上没有重复。

\n
    \n
  • 作用力:语法given xyz: SomeTrait = new SomeTrait { }混乱,并且包含重复的部分
  • \n
  • 解决方案:使用with-syntax,避免重复
  • \n
\n

第五步:不相关的名称

\n

由于listFoo它是由编译器自动调用的,因此我们实际上并不需要该名称,因为我们从不使用它。编译器可以自己生成一些合成名称:

\n
given [A]: Foo[List[A]] with\n  def foo(a: List[A], b: List[A]): List[A] = a ++ b\n  def bar: List[A] = Nil\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  • 作用力:指定人类不使用的不相关名称是很烦人的
  • \n
  • given解决方案:在不需要的地方省略它们的名称。
  • \n
\n

全部一起

\n

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

\n
trait Foo[X]:\n  def foo(a: X, b: X): X\n  def bar: X\n\ndef f[A](xs: List[A])(using foo: Foo[A]): A = xs.foldLeft(foo.bar)(foo.foo)\n\ngiven [A]: Foo[List[A]] with\n  def foo(a: List[A], b: List[A]): List[A] = a ++ b\n  def bar: List[A] = Nil\n\n\nf(List(List(1, 2), List(3, 4)))\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  • s没有重复定义foo/bar方法List
  • \n
  • 无需given显式传递 s,编译器会为我们完成此操作。
  • \n
  • given定义中没有重复的类型
  • \n
  • 没有必要为不适合人类的方法发明不相关的名称。
  • \n
\n