Clojure and me has moved.

Sunday, May 17, 2009

The Need for More Lack of Understanding

This post has moved, go to its new location
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:

mb said...

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))

Christophe Grand said...

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.

mb said...

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. :]