Clojure: уменьшить или применить - PullRequest
122 голосов
/ 01 июля 2010

Я понимаю концептуальную разницу между 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)

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

Ответы [ 9 ]

118 голосов
/ 01 июля 2010

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

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

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

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

48 голосов
/ 03 сентября 2013

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

(apply hash-map [:a 5 :b 6])
;= {:a 5, :b 6}
(reduce hash-map [:a 5 :b 6])
;= {{{:a 5} :b} 6}
20 голосов
/ 01 июля 2010

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

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

19 голосов
/ 01 июля 2010

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

9 голосов
/ 01 июля 2010

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

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

(apply + 1 2 other-number-list)
8 голосов
/ 10 июля 2014

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

(reduce + some-numbers)

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

С apply я должен рассмотреть, какая функция применяется: «ах, это функция +, поэтому я получаю ... одно число».Чуть менее прямолинейно.

6 голосов
/ 12 июля 2014

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

В общем, идея заключается в том, что 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» похоже на «удаление скобок» вокруг последовательности.

4 голосов
/ 11 марта 2015

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

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, уменьшите его довольно чистую рекурсию с помощью internal-Reduce, однако ничего не нашел по реализации apply.Реализация Clojure для + применяет внутренне вызывать Reduce, который кэшируется repl, что, кажется, объясняет 4-й вызов.Кто-нибудь может прояснить, что на самом деле здесь происходит?

3 голосов
/ 15 марта 2018

Прелесть применения данной заданной функции (в данном случае +) может быть применена к списку аргументов, сформированному из предшествующих промежуточных аргументов с конечной коллекцией. 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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...