【问题标题】:Scala3 "as" and "with" keywords used with "given"Scala3“as”和“with”关键字与“given”一起使用
【发布时间】:2022-01-09 17:55:22
【问题描述】:

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

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 编写,会是什么样子?也许这会帮助我弄清楚发生了什么......

【问题讨论】:

  • 您在哪里找到given ... as ... 语法?我不知何故很难在任何地方找到任何提及它的内容?
  • the documentation 和 artima 的书都没有提到它。
  • @AndreyTyukin 我正在学习 Coursera 的 Scala 函数式编程设计课程,在那里我遇到了这种语法,但不幸的是没有解释“as”和“with”关键字。本课程使用 Scala 3。
  • 这是过去试验过的语法,但不是 Scala 3 的一部分。如果这确实在 Coursera 课程中,您可能需要向该课程的维护者报告。
  • @greenTea 是的 AFAIK given ... as ... with: 不存在。特别是as 和: 之后的with。

标签: scala syntax implicit scala-3 given


【解决方案1】:

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 定义中没有重复的类型
  • 没有必要为不适合人类的方法发明不相关的名称。

【讨论】:

  • 感谢您对语法背后的推理进行了非常好的总结。但是,我的问题专门针对关键字及其在此特定上下文中的含义。我仍然没有从你的解释中真正明白这一点。从您写的内容来看,A with { def foo= ... } 似乎只是trait B { def foo; }; A = new B { def foo = ...} 的语法糖但是with 只有在A 是given 时才这样工作吗?这是否在文档中的某处进行了描述? as 关键字仍然让我感到困惑......
  • @greenTea 已更新。我认为你试图解释太多。它实际上只是一个保留关键字:它的唯一目的是将其左侧的代码与右侧的代码分开。
猜你喜欢
  • 2011-07-21
  • 1970-01-01
  • 2014-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-04
  • 1970-01-01
相关资源
最近更新 更多