Using unification to write readable Clojure macros?
In my core.contracts code I’ve experimented with using unification to aide read- and re-readability in my macros. Often I’ve found that I’ll hit a wall when returning to a macro that I wrote long ago. A mass of documentation often helps, but I wanted something more. I think I’ve found it… or at least the beginnings of ‘it’.1 Observe the following macro:2
(defn- build-contract-body
[[args cnstr descr :as V]]
(unify/subst
'(?PARMS
(let [ret ?PRE-CHECK]
?POST-CHECK))
{'?ARGS args
'?F 'f
'?PARMS (vec (list* 'f args))
'?MSG descr
'?PRE-CHECK (build-condition-body
{:pre (:pre cnstr)}
'(apply ?F ?ARGS)
"Pre-condition failure: ")
'?POST-CHECK (build-condition-body
{:post (:post cnstr)}
'ret
"Post-condition failure: ")}))
This macro builds a data-structure that corresponds to a function body useful for tracking pre- and post-condition constraint failures. You’ll see that the meat of the macro is simply:
'(?PARMS
(let [ret ?PRE-CHECK]
?POST-CHECK))
My approach uses unification (subst
from the core.unify library) to fill in the body variables ?PARMS
, ?PRE-CHECK
and ?POST-CHECK
with further data structures. Specifically, the structures to fill are provided in a bindings map to subst
and built directly or via another macro shown below:
(defn- build-condition-body
[constraint-map body prefix-msg]
(unify/subst
'(try
((fn []
?CNSTR
?BODY))
(catch AssertionError ae
(throw (AssertionError. (str ?PREFIX ?MSG \newline (.getMessage ae))))))
{'?CNSTR constraint-map
'?PREFIX prefix-msg
'?BODY body}))
Using this method allows me to effectively draw a picture of the data3 structure representing a function body and fill in the required values via substitution. I need to explore this deeper, so buyer beware, but I like the initial findings.
:F
originally posted on my coderwall profile
-
I hesitate to say that ‘it’ is the ever-elusive self-documenting code. ↩
-
I was considering expanding on this theme in the 2nd edition of the Joy of Clojure, but decided against it. ↩
-
It’s a poor man’s RHS of a
define-syntax
(maybe) so maybe someone should just create adefine-syntax
library. (cough cough) ↩
7 Comments, Comment or Ping
Nick Main
I’m using Racklog in some of my macros – unification does indeed make things cleaner, even compared to sytax-case.
Apr 23rd, 2013
Tim Daly
Michael, if you think this is “readable” you’re going to have a very bad time 5 years from now. I had hoped that you understood the literate software idea. — Tim
Apr 24th, 2013
Meikel
Maybe I’m not getting the point, but in how far is this different to the following:
The
let
is maybe not as fancy as unification, but I always found syntax-quote to quite clearly carry the template nature you claim for the unification approach. (Especially inbuild-condition-body
.Apr 24th, 2013
Matteo Sasso
I don’t understand how that’s different from using symbol-macrolets.
Apr 24th, 2013
fogus
@Daly
I’m not sure what literate programming has to do with my post. I didn’t claim that what I showed was literate. In fact, one of my footnotes makes a joke about the notion that it could be considered literate or self-documenting. Further, I don’t claim to fully grasp literate programming, but I can say that I have tried it to varying degrees and have found a real tension between it and a REPL-driven, evolutionary Lisp style. I’ll keep trying though.
Apr 24th, 2013
fogus
@Meikel
First let me say that, I’m not endorsing this style. Instead, I bring it up because it triggered “good feelings” in me. However, to be honest, if this style has any merit then it’s most likely in that it points at a need for me to try out other macro systems.
Apr 24th, 2013
fogus
@Matteo
Your point is well taken. I will try out the
symbol-macrolet
implementation in clojure.tools.macro. Thanks for prodding me in that direction.Apr 24th, 2013
Reply to “Using unification to write readable Clojure macros?”