PCL -> Clojure, Chapter 6

This article is part of a series describing a port of the samples from Practical Common Lisp (PCL) to Clojure. You will probably want to read the intro first.

This article covers Chapter 6, Variables.

Closures (and STM!)

Functions can close over their lexical environment, in both Common Lisp and Clojure. The PCL example for this is the canonical simple counter. You might think you could do something like this in Clojure:

  ; closes over count, but WON'T WORK
  (def counter (let [count 0] #(inc count)))

This poor counter can only count once:

  user=> (counter)
  1
  
  user=> (counter)
  1

inc returns the increment of counter, but it doesn't change counter. You might look for a setf equivalent in Clojure, but you won't find one. Clojure data structures are immutable.

Of course, we don't really need mutable data here. What we need is a mutable reference to data, that can change to point to a different value later. Clojure provides this via Software Transactional Memory (STM).

Using STM, I can create a ref with ref, and alter a ref with alter. The alter must be inside a transaction, a.k.a. a dosync block.

With all that in mind, here's counter:

  (def counter (let [count (ref 0)] #(dosync (alter count inc))))

This counter actually works. (It is also thread safe).

 
  user=> (counter)
  1
  
  user=> (counter)
  2

Nothing stops multiple functions from closing on the same variables. Here is a function that returns an incrementer, decrementer, and accessor, all sharing the same counter:

  (defn counters []
    (let [count (ref 0)]
      (list #(dosync (alter count inc))
        	  #(dosync (alter count dec))
        	  #(deref count))))

Note that deref does not require a dosync wrapper.

Creating variables

Common Lisp provides defvar and defparameter to create variables. Between them, these forms provide ways to specify an initial value, documentation string, and init-once semantics.

Clojure's support for these ideas lives partially in clojure.contrib.def. Having used def, I can use defvar to specify an initial value and a documentation string:

  (defvar gap-tolerance 0.0001
    "Tolerance to be allowed in widget gaps")

You can then access the value or the docstring:

  user=> gap-tolerance
  1.0E-4
  user=> (doc gap-tolerance)
  -------------------------
  pcl.chap_06/gap-tolerance
    Tolerance to be allowed in widget gaps

For one-time initialization, you can use init-once:

  (def
   #^{:doc "Count of widgets made so far"}
   widget-count)
  (init-once widget-count 0)

In addition to init-once, the form above also demonstrates an alternate docstring syntax. The #^{...} invokes the metadata reader macro. Here the metadata is a docstring, but the mechanism is general. Clojure also uses metadata for visibility (e.g. private), and for adding type information.

Dynamic Binding

In Common Lisp, you can rebind global (dynamic) variables with let. In Clojure, there is a separate binding macro for this purpose. The following example demonstrates the difference between bind and let in Clojure:

  (defn demo-bindings []
    (let [a "let a" b "let b"]
      (print-a-and-b "let"))
    (binding [a "bound a" b "bound b"]
      (print-a-and-b "binding")))

  (defn print-a-and-b [from]
    (println (format "From %s: [a=%s] [b=%s]" from a b)))

This prints:

    user=> (demo-bindings)
    From let: [a=global a] [b=global b]
    From binding: [a=bound a] [b=bound b]
  • The let creates new lexical bindings for a and b, which shadow the global variables. Inside the let's call to print-a-and-b these local bindings are not in scope.
  • The binding creates new dynamic bindings for a and b. These are thread local, but visible for the entire dynamic scope of the binding, including the call to print-a-and-b.

In Clojure, it is idiomatic to use names like *foo* for variables intended for dynamic rebinding.

Wrapping up

Basic creation and binding of variables in Clojure should make sense to a Common Lisp programmer.


Notes

The sample code is available at http://github.com/stuarthalloway/practical-cl-clojure.

Get In Touch