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)