Головоломка выполнения метода в Scala

Сначала объявляю класс:

class Op(var x : Int) {
  def +++(op: Op) = {
    println(this.x + " +++ " + op.x)
    this.x += op.x
    this
  } 
  def ***(op: Op) = {
    println(this.x + " *** " + op.x)
    this.x *= op.x
    this
  }
}

Теперь я выполняю выражение в REPL:

op1 +++ op2 +++ op3 *** op4

и выводит

enter image description here

Но почему метод *** не идет первым? Не является ли приоритет *** выше, чем +++? А как насчет Java и C? Это то же самое, что и в Scala?

Ответ 1

Простые правила

Существует два правила, определяющие порядок оценки выражения op1 +++ op2 +++ op3 *** op4:

Во-первых, поскольку оператор, начинающийся с *, имеет приоритет над оператором, начинающимся с +, выражение преобразуется в:

op1 +++ op2 +++ (op3 *** op4)

Во-вторых, поскольку есть несколько операторов с одинаковым приоритетом, которые появляются бок о бок (op1 +++ op2 +++...), они группируются слева направо:

(op1 +++ op2) +++ (op3 *** op4)

Пример

Рассмотрим следующее выражение:

op1 +++ op2 +++ op3 +++ op4 *** op5

Следуя тем же двум простым правилам, он будет оцениваться как:

((op1 +++ op2) +++ op3) +++ (op4 *** op5)

Другой пример

В качестве альтернативы можно применить те же два правила к op1 +++ op2 +++ op3 +++ op4 *** op5 *** op6:

Операторы, начинающиеся с *, имеют приоритет над операторами, начиная с +:

op1 +++ op2 +++ op3 +++ (op4 *** op5 *** op6)

Тогда операторы группы с одинаковым приоритетом слева направо:

((op1 +++ op2) +++ op3) +++ ((op4 *** op5) *** op6)

Mutable Objects: Слово предостережения

Группирование делает совершенный математический смысл, если методы +++ и *** не имеют побочных эффектов. Рассмотрим:

op1 +++ op2 +++ op1 *** op2

Интуитивно выражение должно возвращать объект, содержащий 5. Однако из-за неудачных побочных эффектов, которые производят методы +++ и *** в исходном образце кода (оба изменяют значение, хранящееся внутри объекта), выражение выражается в объекте, содержащем 12 вместо ожидаемого 5.

Для этого лучше всего полагаться исключительно на неизменяемые объекты при построении таких выражений:

case class Op ( x: Int) {
  def +++(that: Op) = {
    println(this.x + " +++ " + that.x)
    Op(this.x+that.x)
  } 
  def ***(that: Op) = {
    println(this.x + " *** " + that.x)
    Op(this.x * that.x)
  }  
}

Выражение Op(1) +++ Op(2) +++ Op(1) *** Op(2) приведет к Op(5), как и ожидалось.

Ответ 2

op1 +++ op2 +++ op3 *** op4

эквивалентно

((op1 +++ op2) +++ (op3 *** op4))

поскольку вызовы методов являются лево-ассоциативными. Таким образом, сначала оценивается (op1 +++ op2), так как это первый операнд ко второму +++. Затем оценивается второй операнд (op3 *** op4). И, наконец, оценивается внешний оператор.

То же самое было бы верно для op1 + op2 + op3 * op4 в C или Java.