Mark Needham

Thoughts on Software Development

Clojure: The 'apply' function

with 5 comments

In my continued playing around with Clojure I came across the 'apply' function which is used when we want to call another function with a number of arguments but have actually been given a single argument which contains the argument list.

The example that I've been trying to understand is applying 'str' to a collection of values.

I started off with the following:

(str [1 2 3])
=> "[1 2 3]"

This just returns the string representation of the vector that we passed it, but what we actually want is to get an output of "123″.

The 'apply' function allows us to do that:

(apply str [1 2 3])
=> "123"

That is semantically/conceptually the same as doing this:

(str 1 2 3)
=> "123"

I didn't quite understand how that could work though and my assumption was that somewhere in the Clojure source the above function call would be happening.

The definition of 'apply' is as follows:

(defn apply
  "Applies fn f to the argument list formed by prepending args to argseq."
  {:arglists '([f args* argseq])}
  [#^clojure.lang.IFn f & args]
    (. f (applyTo (spread args))))

The first thing which I hadn't realised is that when you have an '&' before a parameter definition then any arguments provided will be put into a list.

If we break down the example above we end up with the following:

(. str (applyTo (spread [[1 2 3]])))

The 'spread' function is defined like so:

1
2
3
4
5
6
7
(defn spread
  {:private true}
  [arglist]
  (cond
   (nil? arglist) nil
   (nil? (next arglist)) (seq (first arglist))
   :else (cons (first arglist) (spread (next arglist)))))

In this case we only have one item in 'arglist' so on line 6 the 'next arglist' expression evaluates to nil.

This means that we create a seq from the first argument of the 'arglist' which is '[1 2 3]'.

Working our way back up to the 'apply' function what we end up with is this:

(. str (applyTo (seq [1 2 3]))) 
=> "123"

This calls through to an 'applyTo' method defined on the 'clojure.lang.IFn' interface.

I'm not sure which of the implementations 'str' maps to but it seems like the 'str' function would eventually be called from the Java code with each of the values in the sequence passed in as a separate argument which is pretty neat!

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • HackerNews
  • StumbleUpon
  • Twitter

Written by Mark Needham

November 25th, 2009 at 11:59 am

Posted in Clojure

Tagged with

5 Responses to 'Clojure: The 'apply' function'

Subscribe to comments with RSS or TrackBack to 'Clojure: The 'apply' function'.

  1. Actually, the magic in the apply method is [#^clojure.lang.IFn f & args] which (I'm pretty sure) is a type assertion. The . is a Java method call, and applyTo is a method defined on IFn, which is the common interface for all functions in Clojure – so that means Java integration is used to do str.applyTo(seq([1 2 3]))

    Ola Bini

    25 Nov 09 at 3:27 pm

  2. Yeah that's right as far as I can see but then inside the 'applyTo' method there seems to be a call to 'fn.invoke()' which I'm assuming calls the Clojure function?

    Mark Needham

    25 Nov 09 at 3:50 pm

  3. I always thought apply was the same as (eval (cons f args))

    Thomas Winant

    26 Nov 09 at 12:12 am

  4. Thanks, you inspired me to look at the innards of Clojure in a way I hadn't before.

    It appears to me that str is itself compiled into an IFun instance (actually two, one extending AFunction and one extending RestFn). Look for (and decompile) core$str* in the clojure.jar. Each of these implement "invoke" according to the logic of str. At this point I frankly don't understand why there are the two different classes.

    As you and Ola already indicated "invoke" is called by applyTo. I believe AFn (AFunction's parent class) implements applyTo as a call to this.invoke with the appropriate parameter count.

    Brian McKeough

    26 Nov 09 at 12:35 pm

  5. @Thomas: "I always thought apply was the same as (eval (cons f args))"

    An important implication of the above article is that apply is lazy with respect to args. (apply (constantly 7) (repeat 1)) evaluates to 7 even though the args list is infinite. The eval-cons cannot do that.

    RafaƂ Dowgird

    4 Mar 10 at 12:07 pm

Leave a Reply