Что лучше?: (уменьшить +...) или (применить +...)?

Должен ли я использовать

(apply + (filter prime? (range 1 20)))

или

(reduce + (filter prime? (range 1 20)))

Изменить: это источник для простого в clojure из оптимизирующего инструментария.

(defn prime?  [n]  
  (cond
    (or (= n 2) (= n 3))          true
    (or (divisible? n 2) (< n 2)) false
    :else                        
     (let [sqrt-n (Math/sqrt n)]
       (loop [i 3]
           (cond
              (divisible? n i) false
              (< sqrt-n i)     true
              :else            (recur (+ i 2)))))))

Ответы

Ответ 1

Если вы спрашиваете с точки зрения производительности, reduce лучше всего:

(time (dotimes [_ 1e6] (apply + (filter even? (range 1 20)))))
"Elapsed time: 9059.251 msecs"
nil

(time (dotimes [_ 1e6] (reduce + (filter even? (range 1 20)))))
"Elapsed time: 8420.323 msecs"
nil

В этом случае разница в 7%, но YMMV зависит от машины.

Вы не предоставили свой источник для функции prime?, поэтому я заменил even? как предикат. Имейте в виду, что в вашей среде исполнения может преобладать prime?, и в этом случае выбор между reduce и apply имеет значение еще меньше.

Если вы спрашиваете, что больше "lispy", я бы сказал, что реализация reduce предпочтительнее, поскольку то, что вы делаете, - это сокращение/списание в смысле функционального программирования.

Ответ 2

Я бы подумал, что reduce будет предпочтительнее, когда он будет доступен, потому что apply использует список как аргументы функции, но когда у вас есть большое число - скажем, миллион элементов в списке, вы построите вызов функции с миллионом аргументов! Это может вызвать некоторые проблемы с некоторыми реализациями Lisp.

Ответ 3

(уменьшить op...) является нормой и (применять op...) исключение (особенно для str и concat).

Ответ 4

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

Сокращение собирает их 1 по одному и свертывает результаты вместе в одно целое, не принимая сразу весь список.

Ответ 5

Я собираюсь играть адвоката дьявола и спорить о apply.

reduce имеет значение clojure принимает fold (точнее foldl), левое сложение и обычно определяется с помощью начального элемента, поскольку операция сложения имеет две части:

  • начальное (или "нулевое" ) значение

  • операция объединения двух значений

поэтому, чтобы найти сумму набора чисел, естественным способом использования + является (fold + 0 values) или, в clojure, (reduce + 0 values).

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

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

[интересно спросить, почему apply чувствует себя более ясным. и я частично думаю, что вы говорите читателю: "Посмотрите, + был разработан, чтобы принимать несколько значений (для чего применяется приложение), поэтому реализация языка будет включать случай нулевых значений". этот неявный аргумент отсутствует с reduce, применяемым к одному списку.]

(reduce + 0 values) также отлично. но (reduce + values) вызывает у меня инстинктивную реакцию: "да, + дает нуль?".

и если вы не согласны, то, пожалуйста, перед тем, как вы опубликуете или опубликуете ответ, уверены ли вы, что (reduce * values) вернется для пустого списка?