13

Should I use

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

or

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

Edit: This is the source for prime in clojure from optimizing toolkit.

(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)))))))
unj2
  • 52,135
  • 87
  • 247
  • 375
  • possible duplicate of [Clojure: reduce vs. apply](http://stackoverflow.com/questions/3153396/clojure-reduce-vs-apply). The linked question is newer than this one, but it has IMO better answers so I'm nominating it as the survivor. – amalloy May 29 '12 at 19:14

6 Answers6

18

If you are asking in terms of performance, the reduce is better by a little:

(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

About 7% difference in this case, but YMMV depending on the machine.

You haven't provided your source for the prime? function, so I have substituted even? as the predicate. Keep in mind that your runtime may be dominated by prime?, in which case the choice between reduce and apply matters even less.

If you are asking which is more "lispy" then I would say that the reduce implementation is preferrable, as what you are doing is a reduce/fold in the functional programming sense.

alanlcode
  • 4,208
  • 3
  • 29
  • 23
14

I would think that reduce would be preferable when it is available, because apply uses the list as arguments to the function, but when you have a large number -- say, a million -- elements in the list, you will construct a function call with a million arguments! That might cause some problems with some implementations of Lisp.

newacct
  • 119,665
  • 29
  • 163
  • 224
  • 8
    Common Lisp has a constant CALL-ARGUMENTS-LIMIT. – Rainer Joswig Aug 02 '09 at 19:55
  • 3
    Good point though not a problem with Clojure - Clojure is happy with constructing arbitrarily long argument lists in this way (even infinite lazy ones as it happens...) – mikera Nov 22 '11 at 01:26
8

(reduce op ...) is the norm and (apply op ...) the exception (notably for str and concat).

cgrand
  • 7,939
  • 28
  • 32
7

i am going to play devil's advocate and argue for apply.

reduce is clojure's take on fold (more exactly foldl), the left-fold, and is usually defined with an initial element, because a fold operation has two parts:

  • an initial (or "zero") value

  • an operation for combining two values

so to find the sum of a set of numbers the natural way to use + is as (fold + 0 values) or, in clojure, (reduce + 0 values).

this explicitly shows the result for an empty list, which is important because it is not obvious to me that + returns 0 in this case - after all, + is a binary operator (all that fold needs or assumes).

now, in practice, it turns out that clojure's + is defined as more than a binary operator. it will take many or, even, zero values. cool. but if we're using this "extra" information it's friendly to signal that to the reader. (apply + values) does this - it says "i am using + in a strange way, as more than a binary operator". and that helps people (me, at least) understand the code.

[it's interesting to ask why apply feels clearer. and i think it's partly that you are saying to the reader: "look, + was designed to accept multiple values (that's what apply is used for), and so the language implementation will include the case of zero values." that implicit argument is not present with reduce applied to a single list.]

alternatively, (reduce + 0 values) is also fine. but (reduce + values) triggers an instinctive reaction in me: "huh, does + provide a zero?".

and if you disagree, then please, before you downvote or post a reply, are you sure about what (reduce * values) will return for an empty list?

andrew cooke
  • 45,717
  • 10
  • 93
  • 143
  • "look, + was designed to accept multiple values (that's what apply is used for), and so the language implementation will signal an error in the case of zero values" is just as plausible. With `reduce + 0` everything is clear, explicit. – Will Ness Oct 19 '22 at 15:14
7

I would expect apply to realize a lazy list which could be ugly, and you never want to assume your list is not lazy, 'cause you could suddenly find yourself getting smacked with massive memory useage.

Reduce is going to grab them 1 by one and roll the results together into a single whole, never taking the whole list in at once.

Runevault
  • 1,492
  • 9
  • 16
  • 4
    Apply in Clojure doesn't actually realise the full argument list unless it needs to. (apply (fn [& args] (take 5 args)) (range)) works fine for example. – mikera Nov 22 '11 at 01:28
0

I agree with Andrew Cooke. But I would like to add that since it already knows how to work and handle multiple arguments, we are essentially re-telling the '+' operator how to work by using it in the 'reduce', in my opinion.

Also, if I told you do to add a bunch of numbers together, you would probably use the '+' operator by itself, and not use 'reduce' at all.

(+ 1 2 3 etc...)

'apply' is just saying, I want to do the same thing, but use what's inside this list for the arguments. There is no extra reasoning required and you are using it as if the numbers didn't exist in a list to begin with, like you would if you were writing it out yourself.

(Reduce is so heavily used that using/reasoning about it in this case isn't a big deal at all, I think either way is fine, but personally prefer apply)

StevenBR
  • 11
  • 4