Managing Per-Thread State with Vars

When you call def or defn, you create a var. In all the examples so far in the book, you pass an initial value to def, which becomes the root binding for the var. For example, the following code creates a root binding for foo of 10:

 (​def​ ^:dynamic foo 10)
 -> #​'user/foo

The binding of foo is shared by all threads. You can check the value of foo on your own thread:

 foo
 -> 10

You can also verify the value of foo from another thread. Create a new thread, passing it a function that prints foo. Don’t forget to start the thread:

 user=>​ (.start (Thread. (​fn​ [] (println foo))))
 -> nil
 | 10

In the previous example, the call to start returns nil, and then the value of foo is printed from a new thread.

Most vars are content to keep their root bindings forever. However, you can create a thread-local binding for a var with the binding macro:

 (binding [bindings] & body)

Bindings have dynamic scope. In other words, a binding is visible anywhere a thread’s execution takes it, until the thread exits the scope where the binding began. A binding is not visible to any other threads.

Structurally, a binding looks a lot like a let. Create a thread-local binding for foo and check its value:

 (binding [foo 42] foo)
 -> 42

To see the difference between binding and let, create a simple function that prints the value of foo:

 (​defn​ print-foo [] (println foo))
 -> #​'user/print-foo

Now, try calling print-foo from both a let and a binding:

 (​let​ [foo ​"let foo"​] (print-foo))
 | 10
 
 (binding [foo ​"bound foo"​] (print-foo))
 | bound foo

As you can see, the let has no effect outside its own form, so the first print-foo prints the root binding 10. The binding, on the other hand, stays in effect through any nested function invocations, so the second print-foo prints bound foo.

Acting at a Distance

Vars intended for dynamic binding are sometimes called special variables. It’s good style to name them with leading and trailing asterisks. For example, Clojure uses dynamic binding for thread-wide options, such as the standard I/O streams *in*, *out*, and *err*. Dynamic bindings enable action at a distance. When you change a dynamic binding, you can change the behavior of distant functions without changing any function arguments.

One kind of action at a distance is temporarily augmenting the behavior of a function. In some languages this would be classified as aspect-oriented programming; in Clojure it’s a side effect of dynamic binding. As an example, imagine that you have a function that performs an expensive calculation. To simulate this, write a function named slow-double that sleeps for a 10th of a second and then doubles its input.

 (​defn​ ^:dynamic slow-double [n]
  (Thread/sleep 100)
  (* n 2))

Next, write a function named calls-slow-double that calls slow-double for each item in [1 2 1 2 1 2]:

 (​defn​ calls-slow-double []
  (map slow-double [1 2 1 2 1 2]))

Time a call to calls-slow-double. With six internal calls to slow-double, it should take a little over six-tenths of a second. Note that you’ll have to run through the result with dorun; otherwise, Clojure’s map will outsmart you by immediately returning a lazy sequence.

 (time (dorun (calls-slow-double)))
 "Elapsed time​:​ 601.418 msecs​"

Reading the code, you can tell that calls-slow-double is slow because it does the same work over and over again. One times two is two, no matter how many times you ask.

Calculations such as slow-double are good candidates for memoization, which we explored in Shortcutting Recursion with Memoization. When you memoize a function, it keeps a cache mapping past inputs to past outputs. If subsequent calls hit the cache, they return almost immediately. Thus, you’re trading space (the cache) for time (calculating the function again for the same inputs).

Clojure provides memoize, which takes a function and returns a memoization of that function:

 (memoize function)

slow-double is a great candidate for memoization, but it isn’t memoized yet, and clients like calls-slow-double already use the slow, unmemoized version. With dynamic binding, this is no problem. Create a binding to a memoized version of slow-double and call calls-slow-double from within the binding.

 (​defn​ demo-memoize []
  (time
  (dorun
  (binding [slow-double (memoize slow-double)]
  (calls-slow-double)))))

With the memoized version of slow-double, calls-slow-double runs three times faster, completing in about two-tenths of a second:

 (demo-memoize)
 "Elapsed time​:​ 203.115 msecs​"

This example demonstrates the power and the danger of action at a distance. By dynamically rebinding a function such as slow-double, you change the behavior of other functions such as calls-slow-double without their knowledge or consent. With lexical binding forms such as let, it’s easy to see the entire range of your changes. Dynamic binding is not so simple. It can change the behavior of other forms in other files, far from the point in your source where the binding occurs.

Used occasionally, dynamic binding has great power. But it should not become your primary mechanism for extension or reuse. Functions that use dynamic bindings are not pure functions and can quickly lose the benefits of Clojure’s functional style.

Working with Java Callback APIs

Several Java APIs depend on callback event handlers. UI frameworks such as Swing use event handlers to respond to user input. XML parsers such as SAX depend on the user implementing a callback handler interface.

These callback handlers are written with mutable objects in mind. Also, they tend to be single threaded. In Clojure, the best way to meet such APIs halfway is to use dynamic bindings. This involves mutable references that feel almost like variables, but because they’re used in a single-threaded setting, they don’t present any concurrency problems.

Clojure provides the set! special form for setting a thread-local dynamic binding:

 (set! var-symbol new-value)

set! should be used rarely. One of the only places in the entire Clojure core that uses set! is the Clojure implementation of a SAX ContentHandler.

A ContentHandler receives callbacks as a parser encounters various bits of an XML stream. In nontrivial scenarios, the ContentHandler needs to keep track of where it is in the XML stream: the current stack of open elements, current character data, and so on.

In Clojure-speak, you can think of a ContentHandler’s current position as a mutable pointer to a specific spot in an immutable XML stream. It’s unnecessary to use references in a ContentHandler, since everything happens on a single thread. Instead, Clojure’s ContentHandler uses dynamic variables and set!. Here is the relevant detail:

 ; redacted from Clojure's xml.clj to focus on dynamic variable usage
 (startElement
  [uri local-name q-name #^Attributes atts]
 ; details omitted
  (set! *stack* (conj *stack* *current*))
  (set! *current* e)
  (set! *state* :element))
 nil)
 (endElement
  [uri local-name q-name]
 ; details omitted
  (set! *current* (push-content (peek *stack*) *current*))
  (set! *stack* (pop *stack*))
  (set! *state* :between)
 nil)

A SAX parser calls startElement when it encounters an XML start tag. The callback handler updates three thread-local variables. The *stack* is a stack of all the elements the current element is nested inside. The *current* is the current element, and the *state* keeps track of what kind of content is inside. (This is important primarily when inside character data, which is not shown here.)

endElement reverses the work of startElement by popping the *stack* and placing the top of the *stack* in *current*.

It’s worth noting that this style of coding is the industry norm: objects are mutable, and programs are single-threadedly oblivious to the possibility of concurrency. Clojure permits this style as an explicit special case, and you should use it for interop purposes only.

The ContentHandler’s use of set! does not leak mutable data into the rest of Clojure. Clojure uses the ContentHandler implementation to build an immutable Clojure structure.

You have now seen four different models for dealing with state. And since Clojure is built atop Java, you can also use Java’s lock-based model. The models and their uses are summarized in the following table. 

Model

Usage

Functions

Refs and STM

Coordinated, synchronous updates

Pure

Atoms

Uncoordinated, synchronous updates

Pure

Agents

Uncoordinated, asynchronous updates

Any

Vars

Thread-local dynamic scopes

Any

Java locks

Coordinated, synchronous updates

Any

 
Let’s put these models to work in designing a small but complete application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.105.2