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
letcreates new lexical bindings foraandb, which shadow the global variables. Inside thelet's call toprint-a-and-bthese local bindings are not in scope. - The
bindingcreates new dynamic bindings foraandb. These are thread local, but visible for the entire dynamic scope of thebinding, including the call toprint-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.
Responses
blog comments powered by Disqus