What do you base that on?

It’s quite interesting to look at the various interfaces that the Clojure implementation objects support, as it gives quite a clue as to what is happening inside the implementation.

The lowest level abstract class is called Obj. This supports the IObj interface which extends IMeta. These interfaces have the following definition and support the association of metadata information with an object. meta() accesses the metadata and withMeta takes some new metadata and makes a new instance with this metadata. This method is abstract in the Obj class.
public interface IMeta {
    IPersistentMap meta();
}
public interface IObj extends IMeta {
    public IObj withMeta(IPersistentMap meta);
}

Meta data is used by the system for many things. The most important example is that information is associated with a Var to record information such as whether it defines a macro as well as location information for its definition. The example below shows the metadata associated with the Var bound to defn. Note that the reader macro #’ can be used to get the Var associated with a symbol and ^ is shorthand for the meta function (which is defined in core.clj).
user=> (var defn)
#’clojure.core/defn
user=> #’defn
#’clojure.core/defn
user=> (meta #’defn)
{:macro true, :ns #<Namespace clojure.core>, :name defn, :file "clojure/core.clj", :line 189, :doc "Same as (def name (fn [params* ] exprs*)) or (defn    name (fn ([params* ] exprs*)+)) with any doc-string or attrs addedn    to the var metadata", :arglists ([name doc-string? attr-map? [params*] body] [name doc-string? attr-map? ([params*] body) + attr-map?])}
user=> ^#’defn
{:macro true, :ns #<Namespace clojure.core>, :name defn, :file "clojure/core.clj", :line 189, :doc "Same as (def name (fn [params* ] exprs*)) or (defn    name (fn ([params* ] exprs*)+)) with any doc-string or attrs addedn    to the var metadata", :arglists ([name doc-string? attr-map? [params*] body] [name doc-string? attr-map? ([params*] body) + attr-map?])}

Because the metadata is a map, and applying a map to a value looks up the value as a key in the map, this means we can easily check the Var for being a macro or get the source location information using:
user=> (^#’defn :macro)
true
user=> (^#’defn :file)
"clojure/core.clj"
user=> (^#’defn :line)
189

The Clojure function with-meta can be used to call the withMeta method. This is defined in core.clj.
(def
#^{:arglists ‘([#^clojure.lang.IObj obj m])
    :doc "Returns an object of the same type and value as obj, with
    map m as its metadata."}
with-meta (fn with-meta [#^clojure.lang.IObj x m]
             (. x (withMeta m))))

user=> (def testVec [1 2 3])
#’user/testVec
user=> (meta testVec)
nil
user=> (def testVec2 (with-meta testVec {:a 1 :b 2}))
#’user/testVec2
user=> (meta testVec2)
{:a 1, :b 2}
user=> testVec2
[1 2 3]

The next interface to look at is IFn which represents something that can be called as function.
public interface IFn extends Callable, Runnable{
  public Object invoke() throws Exception;
  public Object invoke(Object arg1) throws Exception;
  public Object invoke(Object arg1, Object arg2) throws Exception;
  public Object invoke(Object arg1, Object arg2, Object arg3) throws Exception;
  public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4) throws Exception;
  public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) throws Exception;
      ………..
  public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7,
                     Object arg8, Object arg9, Object arg10, Object arg11, Object arg12, Object arg13, Object arg14,
                     Object arg15, Object arg16, Object arg17, Object arg18, Object arg19, Object arg20,
                     Object… args)
        throws Exception;
  public Object applyTo(ISeq arglist) throws Exception;
}
The idea is that performance is optimised by having specialised function versions that reflect the number of arguments that the function expects. So for a simple function that expects two arguments, we’d implement the overload with two arguments and throw an arity exception in the other overloads. The behaviour of throwing an arity exception for all overloads is implemented in the AFn abstract class. This means it is easy to implement a new function by inheriting from this class and overriding the relevant overloads corresponding to the arity of the function.

Some functions can take different numbers of arguments. For these functions several of the overloads would be implemented.
user=> (+ 1 2 3 4)
10

Clojure supports a means of applying a function to an arbitary number of arguments, via the function apply . This is implemented by using the applyTo method of the IFn interface.
user=> (defn foo[x y] (+ x y))
#’user/foo
user=> (apply foo [1 4])
5

AFn implements the applyTo by counting the number of arguments and calling back into the appropriate overload of invoke. AFn also implements run() and call() to support the Runnable and Callable interfaces, where these simply call the invoke() overload.

Clojure functions, defined via the fn special form, are instances of classes that extend AFunction, AFunction is a class which extends AFn, implements the marker (empty) interface Fn, and defines a compare method to allow it to implement the Comparator interface. This compare simply uses the 2 argument invoke overload and then checks the resulting value.
public int compare(Object o1, Object o2){
    try
        {
        Object o = invoke(o1, o2);

        if(o instanceof Boolean)
            {
            if(RT.booleanCast(o))
                return -1;
            return RT.booleanCast(invoke(o2,o1))? 1 : 0;
            }

        Number n = (Number) o;
        return n.intValue();
        }
    catch(Exception e)
        {
        throw new RuntimeException(e);
        }
}

Putting this all together, we find that the following Clojure function, defined in the namespace clojure.test,

  (defn foo[x y] (+ x y))

translates into

public class test$foo__1 extends AFunction
{
  public static final Var const__0 = (Var)RT.var("clojure.core", "+");

  public Object invoke(Object x, Object y) throws Exception { x = null; y = null; return Numbers.add(x, y);
  }
}
(the null assignments are actually carried out after x and y have been pushed on to the stack so they do get passed through to the +. The decompiler is just wrong here!)

And the metadata for the function is set up by the load method in the test__init file for

public class test__init
{
  public static final Var const__0 = (Var)RT.var("clojure.core", "ns");
  public static final Var const__1 = (Var)RT.var("clojure.core", "in-ns");
  public static final AFn const__2 = (AFn)Symbol.create(null, "clojure.test");
  public static final Var const__3 = (Var)RT.var("clojure.core", "refer");
  public static final AFn const__4 = (AFn)Symbol.create(null, "clojure.core");
  public static final Var const__5 = (Var)RT.var("clojure.core", "defn");
  public static final Var const__6 = (Var)RT.var("clojure.test", "foo");
  public static final Object const__7 = Keyword.intern(Symbol.create(null, "file"));
  public static final Object const__8 = Keyword.intern(Symbol.create(null, "line"));
  public static final Object const__9 = Integer.valueOf(3);
  public static final Object const__10 = Keyword.intern(Symbol.create(null, "arglists"));
  public static final Object const__11;
  public static final Var const__12;

  public static void load()
  {
    ((IFn)const__1.get()).invoke(const__2);
    Var tmp38_35 = const__6;
    tmp38_35.bindRoot(new test.foo__1());
    tmp38_35.setMeta((IPersistentMap)RT.map(new Object[] { const__7, "clojure/test.clj", const__8, const__9, const__10, const__11 }));
  }

  // Extra stuff removed
}

user=> (instance? clojure.lang.IFn foo)
true
user=> (instance? clojure.lang.Fn foo)
true
user=> (instance? clojure.lang.IObj foo)
true
user=> (instance? clojure.lang.Obj foo)
true

It’s interesting that things like Var also implement IFn so they can be applied to things. In particular, Var implements the no argument invoke as a lookup on its own value and an invoke on that.
public Object invoke() throws Exception{
    return fn().invoke();
}
final public IFn fn(){
    return (IFn) deref();
}

user=> (defn bar[] 10)
#’user/bar
user=> (#’bar)
10
user=> (var bar)
#’user/bar
user=> ((var bar))
10

Advertisements
This entry was posted in Computers and Internet. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s