I am continuing to translate the examples from On Lisp by Paul Graham into Clojure. You can find links to the other posts in this series and the code on the github repository for this project.
Stuart Halloway has also translated this chapter into Clojure on his blog. I definitely recommend reading that.
In Chapter 7 we finally get to macros.
Section 7.1 How Macro's Work
The nil! macro Graham defines sets the variable passed in to nil. Values in Clojure are immutable by default, but there are special constructs for doing mutation. One is the atom.;; set atom to nil (def x (atom 0)) (reset! x 5) (defmacro atomnil! [var] (list 'reset! var nil)) (atomnil! x)
Ref's must be mutated within a transaction, which is done with dosync.
;; set ref to nil (def y (ref 0)) (dosync (ref-set y 5)) (defmacro refnil! [var] (list 'dosync (list 'ref-set var nil))) (refnil! y)
Section 7.2 Backquote
Clojure also uses the backquote, usually referred to as the syntax quote which can be unquoted. In Clojure unqouting done with the tilde ~ instead of the comma used in Common Lisp.(defmacro atomnil! [var] `(reset! ~var nil)) (defmacro refnil! [var] `(dosync (ref-set ~var nil))) ;; 3 way numerical if (defmacro nif [expr pos zero neg] `(cond (< ~expr 0) ~neg (> ~expr 0) ~pos :else ~zero )) (nif -1 "pos" "zero" "neg")
Just as the , was replaced by ~ `@ becomes ~@
(def b '(1 2 3)) `(a ~b c) ;; => OnLisp.ch7/a (1 2 3) OnLisp.ch7/c `(a ~@b c) ;; => OnLisp.ch7/a 1 2 3 OnLisp.ch7/c
Clojure's do is the equivelant of progn in Common Lisp. It executes a series of statements and returns the value of the last expression.
(defmacro our-when [test & body] `(if ~test (do ~@body))) (our-when (< 1 2) (println "This is a side effect") (println "This is another side effect") "this is a value")
Section 7.3 Defining Special Macrosk
Clojure does not contain a member function, but we can define one, and also define the memq macro which does the same thing.(defn member [obj lst] (some (partial = obj) lst)) (defmacro memq [obj lst] `(some (partial = ~obj) ~lst))
Clojure already has a while loop, which is good, since this implementation isn't very durable.
(defmacro our-while [test & body] `(if ~test (do (swap! ~test not) ~@body))) (our-while (atom true) (println "side effect") "Value")
Section 7.4 Testing Macro Expansion
I like the pretty print macro expansion.(defmacro mac [expr] `(clojure.pprint/pprint (macroexpand-1 '~expr))) (mac (our-while (atom true) (println "side effect") "Value"))That seems like a good amount for one post. Will pick up the second half of chapter 7 next time.