Chris Murphy
Chris Murphy

Reputation: 6509

Definition of function `apply`

To me apply is one of the more difficult functions in Clojure. I'm looking for a good definition of its mechanics - what exactly it does, especially regarding how it 'grabs' arguments that it feeds to the function it is supplied as the first parameter. For instance this definition:

apply explodes a seqable data structure so it can be passed to a function that expects a rest parameter. For example, max takes any number of arguments and returns the greatest of all the arguments.

, from the 'Brave and True' book. Is there more to it than that? It seems to me that apply can be given many 'free' arguments (arguments not in any data structure) and will supply them to the given function. What are the rules for how apply grabs params for its function? What makes it stop?

Upvotes: 0

Views: 151

Answers (2)

Sam Estep
Sam Estep

Reputation: 13324

apply only explodes the last argument. So, for instance, this:

(apply f 1 2 [3 4] 5 [6 7 8])

is equivalent to this:

(apply f (concat [1 2 [3 4] 5] [6 7 8]))

All I've done in this step is take every argument except the last one and put them into a sequence (here I've used a vector, but the exact type doesn't matter), then concatenated that sequence with the last argument (which was already a sequence).

Simplying further, we get this:

(apply f [1 2 [3 4] 5 6 7 8])

which is equivalent to this:

(f 1 2 [3 4] 5 6 7 8)

To better understand the behavior of apply, you can think of it as working like this:

(defn apply [f & args]
  (.applyTo f (seq (lazy-cat (drop-last args) (last args)))))

Just for completeness, this implementation differs from the actual implementation in two ways:

  1. Empty argument list

    ;; actual implementation
    (apply +)
    ;=> ArityException Wrong number of args (1) passed to: core/apply
    
    ;; my implementation
    (apply +)
    ;=> 0
    
  2. Laziness

    ;; actual implementation
    (apply apply == (range))
    ;=> StackOverflowError
    
    ;; my implementation
    (apply apply == (range))
    ;=> false
    

Upvotes: 3

leetwinski
leetwinski

Reputation: 17859

just take a look at its source:

(defn apply
  ([^clojure.lang.IFn f args]
     (. f (applyTo (seq args))))
  ([^clojure.lang.IFn f x args]
     (. f (applyTo (list* x args))))
  ([^clojure.lang.IFn f x y args]
     (. f (applyTo (list* x y args))))
  ([^clojure.lang.IFn f x y z args]
     (. f (applyTo (list* x y z args))))
  ([^clojure.lang.IFn f a b c d & args]
     (. f (applyTo (cons a (cons b (cons c (cons d (spread args)))))))))

where spread is the following:

(defn spread
  [arglist]
  (cond
   (nil? arglist) nil
   (nil? (next arglist)) (seq (first arglist))
   :else (cons (first arglist) (spread (next arglist)))))

so, it takes as many arguments as you provide, making a list of them, expecting the last one to be a sequence, which will be concated to the previous items. And it doesnt matter whether the one of your args, except last one, is a collection, it won't be flattened.

(apply f a b c d e f g [h i j k]) fill call a function with params a b c d e f g h i j k

that's all it does.

Obviously it's up to you to make it correspond the applied fn args, if your function takes two arguments, (apply f [1 2 3 4 5]) will cause an exception.

Upvotes: 2

Related Questions