Что хорошего в правильных ассоциативных методах в 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)(_::_)
,потому что ::
является правоассоциативным.