The Need for More Lack of Understanding

dsl,enlive,moustache — cgrand, 17 May 2009 @ 10 h 25 min

A recent post by Gilad Bracha echoed with my experience designing two small internal DSL in Clojure (Moustache and Enlive’s selectors).

It’s not the same kind of non-understanding which I have in mind. I’m talking about a macro/DSL being able to not understand what is passed to it.

I think it’s an important feature for the user to be able to get out of your DSL and back in regular Clojure.

In both Moustache and Enlive, I avoid to give a special meaning to lists (in Clojure you also have vectors, maps and sets), hence lists always denote user-code and are embedded as-is in the macro expansion. (If I really had to use lists for a DSL, I would check a list doesn’t start with a special value (eg do or unquote (~) — thanks to MB’s comment) before processing it).

That’s why in Enlive you can easily add your own selectors step [:p (my-selector-step arg1 arg2)] as long as (my-selector-step arg1 arg2) evaluates to a correct value (here a state machine).

That’s also how Moustache supports wrapping another Ring handler or custom route validation.

3 Comments »

  1. I use the following approach. It follows a bit Scheme quasiquote.
    The implementation is basically a modified syntax-quote from
    Clojure's compiler. The user can provide anything and verbatim
    code is simply prefix with ~. As in a normal macro.

    Eg:
    (let [x 5]
    (quasiquote (+ ~x 6))

    will give

    (+ 5 6)

    Here is the implementation:

    (defn- unquote?
    "Tests whether the given form is of the form (unquote …)."
    [form]
    (and (seq? form) (= (first form) `unquote)))

    (defn- quasiquote*
    "Worker for quasiquote macro. See docstring there. For use in macros."
    [form]
    (cond
    (self-eval? form) form
    (unquote? form) (second form)
    (symbol? form) (list 'quote form)
    (vector? form) (vec (map quasiquote* form))
    (map? form) (apply hash-map (map quasiquote* (flatten-map form)))
    (set? form) (apply hash-set (map quasiquote* form))
    (seq? form) (list* `list (map quasiquote* form))
    :else (list 'quote form)))

    (defmacro quasiquote
    "Quote the supplied form as quote does, but evaluate unquoted parts.

    Example: (let [x 5] (quasiquote (+ ~x 6))) => (+ 5 6)"
    [form]
    (quasiquote* form))

    Comment by mb — 17 May 2009 @ 11 h 50 min
  2. Hi Meikel,
    I have been bitten by code-walking (the previous Iteration of Enlive used unquote), I think I’ll avoid it for some time (until Clojure compiler is rewritten in Clojure). But I agree ‘unquote is maybe a better choice than ‘do to denote user-code in my current approach.

    Comment by Christophe Grand — 17 May 2009 @ 13 h 08 min
  3. I’m also not a big fan of code walking. In general it bears a lot of (subtle) problems. And I would feel much more comfortable if something like quasiquote was in core complementing quote and syntax-quote. But Rich is not very keen on including quasiquote…

    I saw a lot of its usage in scsh, eg. the shell and regex DSLs. And it worked there quite nicely. But Scheme brings it as a language construct. That is much more robust than a handcrafted hack. :]

    Comment by mb — 17 May 2009 @ 16 h 57 min

RSS feed for comments on this post. TrackBack URI

Leave a comment

(c) 2018 Clojure and me | powered by WordPress with Barecity