Clojure: уменьшить или применить

Я понимаю концептуальную разницу между reduce и apply:

(reduce + (list 1 2 3 4 5))
; translates to: (+ (+ (+ (+ 1 2) 3) 4) 5)

(apply + (list 1 2 3 4 5))
; translates to: (+ 1 2 3 4 5)

Однако какой из них более идиоматичен clojure? Разве это имеет большое значение так или иначе? Из моего (ограниченного) тестирования производительности кажется, что reduce немного быстрее.

Ответ 1

reduce и apply, конечно, только эквивалентны (в терминах конечного результата) для ассоциативных функций, которые должны видеть все их аргументы в случае переменной arity. Когда они будут эквивалентны результатам, я бы сказал, что apply всегда отлично идиоматичен, а reduce эквивалентен - и может сбрить часть мгновенного эффекта глаза - во многих распространенных случаях, Ниже следует мое обоснование для веры в это.

+ сам реализуется в терминах reduce для случая переменной arity (более двух аргументов). Действительно, это кажется очень разумным "дефолтным" способом для любой переменной-атрибутивности, ассоциативной функции: reduce имеет потенциал для выполнения некоторых оптимизаций, чтобы ускорить процесс - возможно, через нечто вроде internal-reduce, 1.2 новинки недавно отключен в мастерстве, но, надеюсь, будет вновь представлен в будущем, что было бы глупо повторять в каждой функции, которая могла бы извлечь из них выгоду в случае vararg. В таких обычных случаях apply просто добавит немного накладных расходов. (Обратите внимание, что ничего не стоит беспокоиться.)

С другой стороны, сложная функция может использовать некоторые возможности оптимизации, которые не являются достаточно общими, чтобы быть встроенными в reduce; то apply позволит вам воспользоваться преимуществами тех, в то время как reduce может на самом деле замедлить вас. Хорошим примером последнего сценария, существующего на практике, является str: он использует StringBuilder внутри себя и значительно выиграет от использования apply, а не reduce.

Итак, я бы сказал, используя apply, когда сомневаюсь; и если вам известно, что он не покупает вам что-либо по сравнению с reduce (и это вряд ли изменится очень скоро), не стесняйтесь использовать reduce, чтобы сбрить эти уменьшающие лишние накладные расходы, если вам это нравится.

Ответ 2

Для новичков, смотрящих на этот ответ,
будьте осторожны, они не совпадают:

(apply hash-map [:a 5 :b 6])
;= {:a 5, :b 6}
(reduce hash-map [:a 5 :b 6])
;= {{{:a 5} :b} 6}

Ответ 3

Мнения меняются. В более широком мире Lisp reduce определенно считается более идиоматичным. Во-первых, обсуждаются вариативные вопросы. Кроме того, некоторые компиляторы Common Lisp будут действительно терпеть неудачу, если apply применяется к очень длинным спискам из-за того, как они обрабатывают списки аргументов.

Среди клауристов в моем кругу, однако, использование apply в этом случае кажется более распространенным. Я нахожу, что это проще, и он тоже предпочитает.

Ответ 4

В этом случае это не имеет никакого значения, потому что + - частный случай, который может применяться к любому числу аргументов. Сокращение - это способ применения функции, которая ожидает фиксированное количество аргументов (2) для произвольно длинного списка аргументов.

Ответ 5

Обычно я предпочитаю уменьшать, действуя на какой-либо коллекции - он хорошо работает и является довольно полезной функцией в целом.

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

(apply + 1 2 other-number-list)

Ответ 6

В этом конкретном случае я предпочитаю reduce, потому что он более доступен для чтения: когда я читаю

(reduce + some-numbers)

Я сразу знаю, что вы превращаете последовательность в значение.

С apply Я должен рассмотреть, какая функция применяется: "ah, это функция +, поэтому я получаю... единственное число". Чуть менее прост.

Ответ 7

При использовании простой функции, такой как +, действительно не имеет значения, какую функцию вы используете.

В общем, идея состоит в том, что reduce - это операция накопления. Вы представляете текущее значение накопления и одно новое значение для своей функции накопления. Результатом функции является накопленное значение для следующей итерации. Итак, ваши итерации выглядят так:

cum-val[i+1] = F( cum-val[i], input-val[i] )    ; please forgive the java-like syntax!

Для применения, идея заключается в том, что вы пытаетесь вызвать функцию, ожидающую несколько скалярных аргументов, но они в настоящее время находятся в коллекции и должны быть извлечены. Итак, вместо того чтобы сказать:

vals = [ val1 val2 val3 ]
(some-fn (vals 0) (vals 1) (vals 2))

мы можем сказать:

(apply some-fn vals)

и он преобразуется в эквивалент:

(some-fn val1 val2 val3)

Таким образом, использование "apply" похоже на "удаление скобок" вокруг последовательности.

Ответ 8

Бит поздно по теме, но я пробовал простой эксперимент после прочтения этого примера. Вот результат моего реплики, я просто ничего не могу вывести из ответа, но, похоже, есть какой-то кеш-ключ между сокращением и применением.

user=> (time (reduce + (range 1e3)))
"Elapsed time: 5.543 msecs"
499500
user=> (time (apply + (range 1e3))) 
"Elapsed time: 5.263 msecs"
499500
user=> (time (apply + (range 1e4)))
"Elapsed time: 19.721 msecs"
49995000
user=> (time (reduce + (range 1e4)))
"Elapsed time: 1.409 msecs"
49995000
user=> (time (reduce + (range 1e5)))
"Elapsed time: 17.524 msecs"
4999950000
user=> (time (apply + (range 1e5)))
"Elapsed time: 11.548 msecs"
4999950000

Глядя на исходный код clojure, уменьшите его довольно чистую рекурсию с помощью inner-reduce, ничего не нашли при реализации приложения. clojure реализация + для применения внутренне вызывать сокращение, которое кэшируется с помощью repl, которые, как представляется, объясняют 4-й вызов. Может кто-то уточнить, что на самом деле происходит здесь?

Ответ 9

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

(apply + 1 2 3 [3 4])
=> 13
(reduce + 1 2 3 [3 4])
ArityException Wrong number of args (5) passed to: core/reduce  clojure.lang.AFn.throwArity (AFn.java:429)