Короткий ответ заключается в том, что правая ассоциативность может улучшить читаемость, делая то, что набирает программист, согласующимся с тем, что на самом деле делает программа. вместо того, чтобы возвращать список в совершенно другом порядке.
Это было бы потому, что '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)
, если только имя метода не заканчивается двоеточием.
Если имя метода заканчивается двоеточием, метод вызывается для правого операнда.< br> Следовательно, в 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 :::.
зачем вообще проводить различие между левоассоциативными и правоассоциативными методами?
Это позволяет сохранить видимость обычной левоассоциативной операции ('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)
Итак, правая ассоциативность заставляет код соответствовать фактическому порядку возвращаемого значения.
person
VonC
schedule
22.07.2009