Что хороший правоассоциативные методы в Scala?

Наблюдение ответов здесь, я думаю, что мы можем прийти к заключению, что большинство из нас действительно приближает порядок алгоритма взгляд в нем и использует здравый смысл вместо того, чтобы вычислить его с, например, основной метод , поскольку о нас думали в университете. После этих слов я должен добавить, что даже преподаватель призвал нас (позже) к на самом деле , думают об этом вместо того, чтобы просто вычислить его.

Также я хотел бы добавить, как это сделано для [1 122] рекурсивные функции :

предполагают, что у нас есть функция как ( код схемы ):

(define (fac n)
    (if (= n 0)
        1
            (* n (fac (- n 1)))))

, который рекурсивно вычисляет факториал данного числа.

первый шаг должен попытаться определить рабочую характеристику для тело функции [только 118] в этом случае, ничто специальное не сделано в теле, просто умножение (или возврат значения 1).

Так производительность для тела: O (1) (постоянный).

Следующий пытаются определить это для количество рекурсивных вызовов . В этом случае у нас есть n-1 рекурсивные вызовы.

Так производительность для рекурсивных вызовов: O (n-1) (порядок является n, поскольку мы выбрасываем незначительные части).

Тогда соединяет те два, и у Вас тогда есть производительность для целой рекурсивной функции:

1 * (n-1) = O (n)

Peter, для ответа поднятые вопросы; метод, который я описываю здесь на самом деле, обрабатывает это вполне хорошо. Но имейте в виду, что это все еще приближение и не полный математически корректный ответ. Метод, описанный здесь, является также одним из методов, которые нам преподавали в университете, и если я помню, правильно использовался для намного более усовершенствованных алгоритмов, чем факториал, который я использовал в этом примере.
, Конечно, все это зависит от того, как хорошо можно оценить время выполнения тела функции и количества рекурсивных вызовов, но это столь же верно для других методов.

31
задан Daniel C. Sobral 22 July 2009 в 19:50
поделиться

2 ответа

Короткий ответ заключается в том, что правоассоциативность может улучшить читаемость, сделав тип программиста совместимым с тем, что программа фактически делает.
Итак, если вы наберете ' 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) для гипотетического метода добавления )

(Напоминание, выдержка из книги Программирование на Scala )
Если метод используется в обозначении оператора, например a * b , метод вызывается для левого операнда, как в a. * (B) - если имя метода не заканчивается в двоеточие.
Если имя метода заканчивается двоеточием, метод вызывается для правого операнда.
Следовательно, в 1 :: twoThree метод :: вызывается для twoThree , передавая 1, например: twoThree .: :( 1) .

Для List он играет роль операции добавления (кажется, что список добавляется после «1», образуя « 1 2 3 », где на самом деле это равно 1, который добавлен к списку ).
Список классов не предлагает настоящую операцию добавления, потому что время, необходимое для добавления в список, растет линейно с размером списка, тогда как добавление с :: требует постоянного времени .
myList :: 1 попытается добавить все содержимое myList к '1', что будет длиннее, чем добавление 1 к myList (как в ' 1 :: myList ')

Примечание: неважно какую ассоциативность имеет оператор, однако его операнды всегда оценивается слева направо.
Итак, если b - это выражение, которое не является простой ссылкой на неизменяемое значение, тогда a ::: b более точно рассматривается как следующий блок:

{ val x = a; b.:::(x) }

В этом блоке a по-прежнему оценивается до b, а затем результат этой оценки передается как операнд методу b :::.


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

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

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

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


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

если вы рассматриваете левоассоциативную функцию 'append', то вы должны написать ' 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)

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

37
ответ дан 27 November 2019 в 22:32
поделиться

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

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

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

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

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

1 :: 2 :: SuperNil

Несколько прискорбно, что правая ассоциативность в настоящее время жестко запрограммирована только для двоеточия в конце, но я думаю, что это позволяет достаточно легко помните.

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

1 :: 2 :: SuperNil

Несколько прискорбно, что правая ассоциативность в настоящее время жестко запрограммирована только для двоеточия в конце, но я думаю, что это позволяет достаточно легко помните.

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

1 :: 2 :: SuperNil

К сожалению, правая ассоциативность в настоящее время жестко запрограммирована только для двоеточия в конце, но я думаю, что это позволяет достаточно легко помните.

3
ответ дан 27 November 2019 в 22:32
поделиться
Другие вопросы по тегам:

Похожие вопросы: