Why should I use 'apply' in Clojure?

16,999

Solution 1

You would use apply, if the number of arguments to pass to the function is not known at compile-time (sorry, don't know Clojure syntax all that well, resorting to Scheme):

(define (call-other-1 func arg) (func arg))
(define (call-other-2 func arg1 arg2) (func arg1 arg2))

As long as the number of arguments is known at compile time, you can pass them directly as is done in the example above. But if the number of arguments is not known at compile-time, you cannot do this (well, you could try something like):

(define (call-other-n func . args)
  (case (length args)
    ((0) (other))
    ((1) (other (car args)))
    ((2) (other (car args) (cadr args)))
    ...))

but that becomes a nightmare soon enough. That's where apply enters the picture:

(define (call-other-n func . args)
  (apply other args))

It takes whatever number of arguments are contained in the list given as last argument to it, and calls the function passed as first argument to apply with those values.

Solution 2

The terms Lisp-1 and Lisp-2 refer to whether functions are in the same namespace as variables.

In a Lisp-2 (that is, 2 namespaces), the first item in a form will be evaluated as a function name — even if it's actually the name of a variable with a function value. So if you want to call a variable function, you have to pass the variable to another function.

In a Lisp-1, like Scheme and Clojure, variables that evaluate to functions can go in the initial position, so you don't need to use apply in order to evaluate it as a function.

Solution 3

apply basically unwraps a sequence and applies the function to them as individual arguments.

Here is an example:

(apply + [1 2 3 4 5])

That returns 15. It basically expands to (+ 1 2 3 4 5), instead of (+ [1 2 3 4 5]).

Solution 4

You use apply to convert a function that works on several arguments to one that works on a single sequence of arguments. You can also insert arguments before the sequence. For example, map can work on several sequences. This example (from ClojureDocs) uses map to transpose a matrix.

user=> (apply map vector [[:a :b] [:c :d]])
([:a :c] [:b :d])

The one inserted argument here is vector. So the apply expands to

user=> (map vector [:a :b] [:c :d])

Cute!

PS To return a vector of vectors instead of a sequence of vectors, wrap the whole thing in vec:

user=> (vec (apply map vector [[:a :b] [:c :d]]))

While we're here, vec could be defined as (partial apply vector), though it isn't.

Concerning Lisp-1 and Lisp-2: the 1 and 2 indicate the number of things a name can denote in a given context. In a Lisp-2, you can have two different things (a function and a variable) with the same name. So, wherever either might be valid, you need to decorate your program with something to indicate which you mean. Thankfully, Clojure (or Scheme ...) allows a name to denote just one thing, so no such decorations are necessary.

Solution 5

The usual pattern for apply type operations is to combine a function provided at runtime with a set of arguments, ditto.

I've not done enough with clojure to be able to be confident about the subtleties for that particular language to tell whether the use of apply in that case would be strictly necessary.

Share:
16,999
unj2
Author by

unj2

.

Updated on June 06, 2022

Comments

  • unj2
    unj2 almost 2 years

    This is what Rich Hickey said in one of the blog posts but I don't understand the motivation in using apply. Please help.

    A big difference between Clojure and CL is that Clojure is a Lisp-1, so funcall is not needed, and apply is only used to apply a function to a runtime-defined collection of arguments. So, (apply f [i]) can be written (f i).

    Also, what does he mean by "Clojure is Lisp-1" and funcall is not needed? I have never programmed in CL.

    Thanks

  • Isaiah
    Isaiah over 12 years
    Yet another example: (apply + 1 2 [3 4 5]) => 15
  • ianjs
    ianjs over 10 years
    ...but this fails (apply + 1 2 [3 4 5]) which was unexpected. Any idea why? (I'm completely new to Clojure)
  • ianjs
    ianjs over 8 years
    hmmm.. just typed that into a REPL and you're right, it works fine. Must have been something about the environment I had set up nearly two years ago.