Ответ 1
Короткий ответ заключается в том, что право-ассоциативность может улучшить читаемость, сделав то, что тип программиста соответствует тому, что действительно делает программа.
Итак, если вы наберете "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
, против O (n) для гипотетического методаappend
)
(Напоминание, извлечение из книги Программирование в Scala)
Если в операторной нотации используется метод, например a * b
, метод вызывается в левом операнде, как в a.*(b)
- если имя метода не заканчивается в двоеточие.
Если имя метода заканчивается в двоеточие, метод вызывается в правом операнде.
Поэтому в 1 :: twoThree
метод ::
вызывается на twoThree
, передавая в 1, как это: twoThree.::(1)
.
Для списка он играет роль операции добавления (список, кажется, добавляется после "1", чтобы сформировать "1 2 3
", где на самом деле это 1, который добавлен к списку).
Список классов не предлагает действительную операцию добавления, поскольку время, которое требуется для добавления в список, растет линейно с размером списка, тогда как добавление с:: принимает постоянное время. myList :: 1
будет пытаться довести все содержимое myList до '1', которое будет длиннее, чем добавление 1 к myList (как в '1 :: myList
')
Примечание. Независимо от того, какая ассоциативность имеет оператор, однако, его операнды
всегда оценивается слева направо.
Поэтому, если b - это выражение, которое является не просто ссылкой на неизменяемое значение, то a: b более точно рассматривается как следующий блок:
{ val x = a; b.:::(x) }
В этом блоке a все еще оценивается до b, а затем результат этой оценки передается в качестве операнда для метода bs:.
зачем вообще различать лево-ассоциативные и право-ассоциативные методы?
Это позволяет сохранить обычную левоассоциативную операцию ('1 :: myList
'), фактически применяя операцию в правильном выражении, потому что:
- он более эффективен.
- но это более читаемо с помощью обратного ассоциативного порядка ('
1 :: myList
' vs. 'myList.prepend(1)
')
Итак, как вы говорите, "синтаксический сахар", насколько я знаю.
Обратите внимание, что в случае foldLeft
, например, они могли бы переместиться немного далеко (с помощью '/:
ассоциативный эквивалент оператора)
Чтобы включить некоторые ваши комментарии, слегка перефразируйте:
если вы рассматриваете функцию "добавить", лево-ассоциативную, тогда вы должны написать "oneTwo append 3 append 4 append 5
".
Однако, если бы это было добавление 3, 4 и 5 к одному двум (которое вы предположили бы так, как оно написано), это будет O (N).
То же самое с "::", если это было для "append". Но это не так. Это на самом деле для "preend"
Это означает, что 'a :: b :: Nil
' для 'List[].b.prepend(a)
'
Если '::' должны были добавляться и оставаться лево-ассоциативными, тогда результирующий список будет в неправильном порядке.
Вы ожидали бы, что он вернет List (1, 2, 3, 4, 5), но он вернет список List (5, 4, 3, 1, 2), что может быть неожиданным для программиста.
Это потому, что вы сделали бы в лево-ассоциативном порядке:
(1,2).prepend(3).prepend(4).prepend(5) : (5,4,3,1,2)
Таким образом, правая ассоциативность делает код совпадающим с фактическим порядком возвращаемого значения.