Reputation: 6509
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
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:
Empty argument list
;; actual implementation
(apply +)
;=> ArityException Wrong number of args (1) passed to: core/apply
;; my implementation
(apply +)
;=> 0
Laziness
;; actual implementation
(apply apply == (range))
;=> StackOverflowError
;; my implementation
(apply apply == (range))
;=> false
Upvotes: 3
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