read


read
or learn more

Using unification to write readable Clojure macros?

Apr 23, 2013

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


  1. I hesitate to say that ‘it’ is the ever-elusive self-documenting code

  2. I was considering expanding on this theme in the 2nd edition of the Joy of Clojure, but decided against it. 

  3. It’s a poor man’s RHS of a define-syntax (maybe) so maybe someone should just create a define-syntax library. (cough cough) 

7 Comments, Comment or Ping

  1. I’m using Racklog in some of my macros – unification does indeed make things cleaner, even compared to sytax-case.

  2. 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

  3. Maybe I’m not getting the point, but in how far is this different to the following:

    (defn- build-condition-body
      [constraint-map body prefix-msg]
      `(try
         ((fn []
            ~contstraint-map
            ~body))
         (catch AssertionError ae
           (throw
             (AssertionError.
               (str ~prefix ~msg \newline (.getMessage ae)))))))
    
    (defn- build-contract-body
      [[args cnstr descr :as V]]
      (let [f          (gensym "f")
            ret        (gensym "ret")
            parms      (into [f] args)
            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: ")]
        `(~parms
           (let [ret ~pre-check]
             ~post-check))))
    

    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 in build-condition-body.

  4. Matteo Sasso

    I don’t understand how that’s different from using symbol-macrolets.

  5. @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.

  6. @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.

  7. @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.

Reply to “Using unification to write readable Clojure macros?”