为什么运算符关联性对于 Scala 3 扩展方法和常规方法的工作方式不同?

问题描述

当我手动将 Scala 2 代码转换为 Scala 3 时,我的 DSL 的运算符优先级发生了变化,我花了很长时间进行调试和修复。 : 的处理似乎有所不同:

      extension (i1: Int) def ~>:(i2: Int) = i1 < i2
      extension (i1: Int) def ~>(i2: Int) = i1 < i2

      class Wrap(val i: Int):
        def ~>:(w: Wrap) = i ~>: w.i
        def ~>(w: Wrap) = i ~> w.i

      // `Wrap` preserves `~>`
      println(1 ~> 2) // true
      println(Wrap(1) ~> Wrap(2)) // true

      // `Wrap` does not preserve `~>:`
      println(1 ~>: 2) // true
      println(Wrap(1) ~>: Wrap(2)) // false

我的心智模型是:

我的心智模型似乎是错误的。解释正在发生的事情的正确方法是什么?

链接会有所帮助,我检查了 Scala 3 文档,但没有找到有关自定义运算符如何关联的任何信息。

更新

我尝试在 infix 之前添加 def 关键字,但它不会更改此示例中打印的内容

解决方法

你的心智模型只需要进行一些调整。

回想一下,中缀 x op y 去糖化为 x.op(y),除非操作以冒号结尾,否则它是 y.op:(x)。无论 op() 方法是实例参数的原生方法还是附加的扩展,这都适用,在 Scala-2 中,由中间隐式类处理。

implicit class IntermediateClass(instance: Int) {
  def op(arg: Int) = ???
}

另一方面,Scala-3 extension 只是一个接收两个柯里化参数的方法。所以中缀调用 leftOfOp op rightOfOp 将始终这样处理:

extension (leftOfOp: Int)
  def op(rightOfOp: Int) = ???

无论 op 是否有尾随 : 都是一样的。但是,虽然定义站点的代码以这种方式保持一致,但调用站点的关联性却如您所料。

extension (left: String)
  def @:(right: String):String = s"$left.@:($right)"
  def @@(right: String):String = s"$left.@@($right)"

"TOP" @: "MID" @: "END"  //"TOP.@:(MID.@:(END))"
"top" @@ "mid" @@ "end"  //"top.@@(mid).@@(end)"

可以在 herehere 中找到更多详细信息。