Что хорошего в правильных ассоциативных методах в Scala?

Я только начал играть с Scala, и я только что узнал о том, как можно сделать методы право-ассоциативной (в отличие от более традиционных лево-ассоциативность common в императивных объектно-ориентированных языках).

сначала, когда я увидел пример кода cons список в Scala, я заметил, что каждый пример всегда имел список с правой стороны:

println(1 :: List(2, 3, 4))
newList = 42 :: originalList

однако, даже после просмотра этого снова и снова опять же, я не думал дважды об этом, потому что я не знал (в то время), что :: метод List. Я просто предположил, что это оператор (опять же, в смысле оператора на Java), и что ассоциативность не имеет значения. Дело в том, что List всегда появлялся справа в примере кода, просто казался случайным (я думал, что это, возможно, просто "предпочтительный стиль").

теперь я знаю лучше: это должно быть написано так, потому что :: является правоассоциативным.

мой вопрос, какой смысл быть в состоянии определить право-ассоциативные методы?

это чисто по эстетическим причинам, или правая ассоциативность действительно может иметь какое-то преимущество перед левой ассоциативностью в определенных ситуациях?

С моей (новичка) точки зрения, я действительно не вижу, как

1 :: myList

лучше, чем

myList :: 1

но это, очевидно, такой тривиальный пример, что я сомневаюсь, что это справедливое сравнение.

3 ответов


короткий ответ заключается в том, что правая ассоциативность может улучшить читаемость, сделав то, что тип программиста согласуется с тем, что программа на самом деле делает.
Итак, если вы наберете'1 :: 2 :: 3', вы получаете Список (1, 2, 3) обратно, вместо того, чтобы получить список обратно в совершенно другом порядке.
Это было бы потому, что"1 :: 2 :: 3 :: Nil' на самом деле

List[Int].3.prepend(2).prepend(1)

scala> 1 :: 2 :: 3:: Nil
res0: List[Int] = List(1, 2, 3)

это как:

  • более читабельным
  • более эффективный (O (1) для prepend, vs. O(n) для гипотетического append способ)

(напоминание, выписка из книги программирование в Scala)
Если метод используется в нотации оператора, например a * b, метод вызывается в левом операнде, как в a.*(b) - если имя метода не заканчивается двоеточием.
Если имя метода заканчивается двоеточием, метод вызывается в правом операнде.
Следовательно, in 1 :: twoThree на :: метод вызывается на twoThree, проходящей в 1, вот так:twoThree.::(1).

для списка он играет роль операции добавления (список, похоже, добавляется после "1", чтобы сформировать"1 2 3', где на самом деле это 1, который начинаются в списке).
Class List не предлагает истинную операцию добавления, потому что время, необходимое для добавления в список, растет линейно с размером списка, тогда как prepending с :: принимает постоянн время.
myList :: 1 попытался бы добавить весь содержание "мой список" на "1", который будет больше, чем добавляя 1 в списке "мой список" (как в '1 :: myList')

Примечание: независимо от того, какую ассоциативность имеет оператор, однако его операнды всегда оценивается слева направо.
Поэтому, если B-это выражение, которое не является простой ссылкой на неизменяемое значение, то a::: b более точно рассматривается как следующий блок:

{ val x = a; b.:::(x) }

в этом блоке a все еще оценивается перед b, а затем результат этой оценки есть передается как операнд в метод b:::.


зачем делать различие между лево-ассоциативные и право-ассоциативные методы вообще?

, что позволяет сохранить внешний вид обычного левую ассоциативность операции ('1 :: myList') при фактическом применении операции к правильному выражению, потому что;

  • это более эффективно.
  • но он более читаем с обратным ассоциативным порядком ('1 :: myList' и 'myList.prepend(1)')

так как вы говорите, "синтаксический сахар", насколько я знаю.
Обратите внимание, в случае foldLeft, например, они могли бы ушел немного далеко (С '' операторные)


чтобы включить некоторые из ваших комментариев, слегка перефразируйте:

если вы рассматриваете функцию "append", левоассоциативную, то вы напишете"oneTwo append 3 append 4 append 5'.
Однако, если он должен был добавить От 3, 4 и 5 до oneTwo (что вы предполагаете, кстати, это написано), это будет O(N).
То же самое с'::', если это было для "append". Но это не так. Это на самом деле для "подставляла"

что означает 'a :: b :: Nil 'is for'List[].b.prepend(a)'

если бы':: 'было добавлено и все же оставалось левоассоциативным, то результирующий список был бы в неправильном порядке.
Вы ожидаете, что он вернет список(1, 2, 3, 4, 5), но это приведет к возвращению списка(5, 4, 3, 1, 2), что может быть неожиданный для программиста.
Это потому, что вы сделали бы, в левой-ассоциативном порядке:

(1,2).prepend(3).prepend(4).prepend(5) : (5,4,3,1,2)

таким образом, правая ассоциативность делает код совпадающим с фактическим порядком возвращаемого значения.


какой смысл быть в состоянии определить право-ассоциативные методы?

Я думаю, что точка правоассоциативного метода заключается в том, чтобы дать кому-то возможность расширить язык, который является точкой переопределения оператора в целом.

перегрузка оператора-полезная вещь, поэтому Scala сказал: Почему бы не открыть его для любой комбинации символов? Скорее, зачем проводить различие между операторами и методами? Теперь, как реализует библиотека взаимодействуют со встроенными типами, такими как Int? В C++, она бы использовать friend функции в глобальной области видимости. Что делать, если мы хотим все типы для реализации оператора ::?

правая ассоциативность дает чистый способ добавить :: оператора для всех типов. Конечно, технически :: оператор-это метод типа список. Но это также воображаемый оператор для встроенных Int и все других типов, по крайней мере, если вы можете игнорировать :: Nil в конце.

Я думаю, что это отражает философию Scala о реализации как можно больше вещей в библиотеке и делает язык гибким для их поддержки. Это дает возможность кому-то придумать Суперлист, который можно назвать:

1 :: 2 :: SuperNil

несколько неудачно, что правильная ассоциативность в настоящее время жестко закодирована только в двоеточии в конце, но я думаю, что это делает его достаточно легким для запоминания.


правая ассоциативность и левая ассоциативность играют важную роль при выполнении операций сгиба списка. Например:

def concat[T](xs: List[T], ys: List[T]): List[T] = (xs foldRight ys)(_::_)

это работает прекрасно. Но вы не можете выполнить то же самое с помощью операции foldLeft

def concat[T](xs: List[T], ys: List[T]): List[T] = (xs foldLeft ys)(_::_)

,потому что :: является правоассоциативным.