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