How tight is the binding?

Clojure has a notion of binding that is close to the behaviour of special variables of Common Lisp. Using def, and associated macros such as defn, one can set up a location that is associated with a symbol and which can take thread specific values.

Define a Var associated with the symbol boo.
user=> (def boo 10)
#’user/boo

Evaluating the symbol boo, looks for the associated Var and then gets the value of the Var.
user=> boo
10

Define a function, bound to the symbol foo which returns the value of the Var associated with boo.
user=> (defn foo [] boo)
#’user/foo

Note that defn is just a macro that expands into a use of def
user=> (macroexpand ‘(defn foo[] boo))
(def foo (clojure.core/fn ([] boo)))

Define a function that calls foo which in turn returns the value of the Var associated with boo.
user=> (defn bar [] (foo))
#’user/bar
user=> (bar)
10

Now we can use the binding macro to rebind the value of the Var cell associated with boo during the evaluation of bar.
user=> (binding [boo 30] (bar))
30

This is completely different from what a let binding does.
user=> (let [boo 30] (bar))
10

But, Var are not just used for values like integers, but are also used to hold the function values too.
user=> (binding [foo (fn [] 50)] (bar))
50

So how does this all work then? Well, the foo function looks something like this:
public class test$foo__1 extends AFunction
{
  public static final Var const__0 = (Var)RT.var("clojure.test", "boo");

  public Object invoke() throws Exception { return const__0.get();
  }
}
As this shows, accessing the value is achieved by calling the get() method of the Var object.

Binding is a macro that is responsible for setting up new mappings.
user=> (macroexpand ‘(binding [boo 30] (bar)))
(let*
[]
(clojure.core/push-thread-bindings (clojure.core/hash-map #’boo 30))
(try (bar) (finally (clojure.core/pop-thread-bindings))))

The Var class has a static ThreadLocal field that contains a chain of Frames for that particular thread.
static ThreadLocal<Frame> dvals = new ThreadLocal<Frame>(){
    protected Frame initialValue(){
        return new Frame();
    }
};

A Frame is an object with three fields.
  Associative bindings;             // All bindings in the chain of Frames – if a Var is bound its latest value for the thread is found here
  Associative frameBindings;    // The bindings set for the current frame
  Frame prev;                        // The next Frame in the chain

Pushing new bindings involves getting the current bindings, merging in the new bindings and then adding a Frame containing this information to the thread local variable. The AtomicInteger in the field count is used to optimise the case when there are no bindings for the given Var on any thread.
public static void pushThreadBindings(Associative bindings){
    Frame f = dvals.get();
    Associative bmap = f.bindings;
    for(ISeq bs = bindings.seq(); bs != null; bs = bs.next())
        {
        IMapEntry e = (IMapEntry) bs.first();
        Var v = (Var) e.key();
        v.validate(v.getValidator(), e.val());
        v.count.incrementAndGet();
        bmap = bmap.assoc(v, new Box(e.val()));
        }
    dvals.set(new Frame(bindings, bmap, f));
}

Note that the value stored in the map is of type Box, an instance with a single value field. This is required as Var instances support a set function which changes the value of the last binding.
  user=> (def foo 10)
  #’user/foo
  user=> foo
  10
  user=> (defn getFoo[] foo)
  #’user/getFoo
  user=> (getFoo)
  10
  user=> (binding [foo 30] (var-set #’foo 50) (getFoo))
  50
  user=> foo
  10

The Var get method is the same as the deref method
final public Object deref(){
    Box b = getThreadBinding();
    if(b != null)
        return b.val;
    if(hasRoot())
        return root;
    throw new IllegalStateException(String.format("Var %s/%s is unbound.", ns, sym));
}

This uses getThreadBinding to find the latest frame
final Box getThreadBinding(){
    if(count.get() > 0)
        {
        IMapEntry e = dvals.get().bindings.entryAt(this);
        if(e != null)
            return (Box) e.val();
        }
    return null;
}

If there is no binding on the thread, then we check for a root binding, stored in the field root. The dvals value itself is stored in the root field to signal that there is no root binding. otherwise the root field contains the root value itself.
final public boolean hasRoot(){
    return root != dvals;
}

In the code to set up the bindings, there was also a call to validate. Vars can have a validation function associated with them that can be used to verify values that are being set into them. For example, we can ensure that the value in the Var associated with myFunc is always a function; attempting to bind it to an integer causes the binding to throw an exception.
  user=> (defn myFunc [])
  #’user/myFunc
  user=> (set-validator! #’myFunc fn?)
  nil
  user=> (def myFunc 10)
  #<CompilerException java.lang.IllegalStateException: Invalid reference state (NO_SOURCE_FILE:153)>
  user=> myFunc
  #<user$myFunc__4974 user$myFunc__4974@19902f7>

Note that we can get rid of the validation function
  user=> (set-validator! #’myFunc nil)
  nil
  user=> (def myFunc 10)
  #’user/myFunc

This validation logic in inherited by Var from the ARef class.

There are a number of utility functions for setting the root binding of a Var without affecting the values seen by individual threads and for unbinding the root binding. There are also functions for dealing the metadata associated with the Var. This metadata is used for flagging whether the Var represents a function or a macro for example.

About these ads
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