Вопрос

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

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

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

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

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

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

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

С моей (новичковой) точки зрения, я не совсем понимаю, как

1 :: myList

это лучше, чем

myList :: 1

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

Это было полезно?

Решение

Короткий ответ: правая ассоциативность может улучшить читабельность, делая то, что пишет программист, совместимым с тем, что на самом деле делает программа.
Итак, если вы наберете '1 :: 2 :: 3', вы получаете обратно List(1, 2, 3) вместо того, чтобы возвращать List в совершенно другом порядке.
Это было бы потому, что '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, а затем результат этой оценки передается как операнд B -b :::метод.


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

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

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

Насколько я знаю, как вы говорите, «синтаксический сахар».
Обратите внимание: в случае foldLeft, например, они могут иметь зашел слишком далеко (с '/:'эквивалент правоассоциативного оператора)


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

если вы рассматриваете функцию добавления, левоассоциативную, вы должны написать 'oneTwo append 3 append 4 append 5'.
Однако если бы к oneTwo пришлось присоединить 3, 4 и 5 (что можно было бы предположить по тому, как оно написано), это было бы 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)

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

Другие советы

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

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

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

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

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

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)(_::_)

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top