Что могло вызвать следующую перегрузку оператора для замены операндов?

Я использую язык scala для определения двух операторов: :++ и ++:, которые служат точным зеркалом друг друга: a :++ b == b ++: a, они явно не коммутативны: a :++ b != a ++: b.

Это мой код scala для тестирования:

import org.scalatest.FunSpec

import scala.collection.immutable.ListMap

case class Example(self: ListMap[String, String] = ListMap.empty) {

  def :++(v: Example) = this.copy(
    self ++ (v.self -- self.keys.toSeq)
  )

  def ++:(v: Example) = {
    println("forward: " + :++(v))
    println("reverse: " + (v :++ this))
    v :++ this
  }
}

class OperatorOverrideSuite extends FunSpec {

  val p1 = Example(ListMap("a" -> "1"))
  val p2 = Example(ListMap("a" -> "2"))

  it(":++ operator should preserve first value") {
    assert(p1 :++ p2 == p1)
  }

  it("++: operator should preserve second value") {
    assert(p1 ++: p2 == p2)
  }
}

Первый тест выглядит хорошо, но при запуске я получил следующую ошибку:

forward: Example(Map(a -> 2))
reverse: Example(Map(a -> 1))

Example(Map(a -> 1)) did not equal Example(Map(a -> 2))
ScalaTestFailureLocation: com.schedule1.datapassports.params.OperatorOverrideSuite$$anonfun$2 at (OperatorOverrideSuite.scala:30)
Expected :Example(Map(a -> 2))
Actual   :Example(Map(a -> 1))
 <Click to see difference>

org.scalatest.exceptions.TestFailedException: Example(Map(a -> 1)) did not equal Example(Map(a -> 2))
    at org.scalatest.Assertions$class.newAssertionFailedException(Assertions.scala:528)
    at org.scalatest.FunSpec.newAssertionFailedException(FunSpec.scala:1630)
    at org.scalatest.Assertions$AssertionsHelper.macroAssert(Assertions.scala:501)
    at ...

из печатного сообщения видно, что scala переопределяет мой оператор и меняет операнды самостоятельно, что может заставить компилятор scala вести себя таким образом? Это ошибка?

Я использую последнюю версию scala 2.11 и последнюю версию Java 8u181 для тестирования.


person tribbloid    schedule 21.09.2018    source источник


Ответы (1)


Из спецификации языка Scala. :

Ассоциативность оператора определяется последним символом оператора. Операторы, оканчивающиеся двоеточием `:', правоассоциативны. Все остальные операторы левоассоциативны.

Вот демонстрация этого:

scala> case class Test(name: String) {
     |     def ++:(that: Test) = println(s"Called ++: on $this with argument $that")
     |     def :++(that: Test) = println(s"Called :++ on $this with argument $that")
     | }
defined class Test

scala> val (x, y) = (Test("x"), Test("y"))
x: Test = Test(x)
y: Test = Test(y)

scala> x ++: y
Called ++: on Test(y) with argument Test(x)

scala> x :++ y
Called :++ on Test(x) with argument Test(y)

В результате, когда вы говорите p1 ++: p2 в своем коде, выполняется p2.++:(p1), что эквивалентно p1 :++ p2. Это означает, что ваши два оператора на самом деле строго эквивалентны.

person Dici    schedule 21.09.2018
comment
ничего себе, я никогда раньше не видел этого правила, это одно из SIP? и что произойдет, если оператор будет :+:: ? - person tribbloid; 21.09.2018
comment
Я пытаюсь найти его в каком-то документе, но пока не смог. Я думаю, что научился этому, когда некоторое время назад читал Programming in Scala от Odersky. - person Dici; 21.09.2018
comment
@tribbloid: это так же старо, как и сама Scala, задолго до того, как существовал процесс SIP. Без правоассоциативных операторов один из самых важных синтаксисов в Scala, someElement :: someList, не работал бы. Я действительно удивлен, что вы никогда раньше не сталкивались с таким синтаксисом. - person Jörg W Mittag; 22.09.2018