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.
I was writing something along these lines:
(loop [state init, n (count some-seq)]
(if (pos? n)
(recur value (dec n))
(ends here)))
when it struck me that seqs are numerals too!
(loop [state init, n some-seq]
(if (seq n)
(recur value (rest n))
(ends here)))
or:
(loop [state init, n (seq some-seq)]
(if n
(recur value (next n))
(ends here)))
I just uploaded a library that builds on zippers but shifts emphasis from nodes to insertion-points (interstitial positions before, between and after nodes).
It eases growing trees from an empty root.
; show-ip represents the currently edited structure with * denoting the insertion-point
(defn show-ip [ip] (-> ip (insert-left '*) first z/root))
(def e (-> [] z/vector-zip (insertion-point :append)))
(-> e show-ip) ; [*]
(-> e (insert-left 1) show-ip) ; [1 *]
(-> e (insert-left 1) (insert-right 2) show-ip) ; [1 * 2]
(-> e (insert-left 1) (insert-right 2) left show-ip) ; [* 1 2]
(-> e (insert-left [1 2]) show-ip) ; [[1 2] *]
(-> e (insert-left [1 2]) left show-ip) ; [* [1 2]]
(-> e (insert-left [1 2]) left right show-ip) ; [[1 2] *]
(-> e (insert-left [1 2]) left next show-ip) ; [[* 1 2]]
(-> e (insert-left [1 2]) left next next show-ip) ; [[1 * 2]]
(-> e (insert-left [1 2]) left next next next show-ip) ; [[1 2 *]]
(-> e (insert-left [1 2]) left next next next next show-ip) ; [[1 2] *]
(-> e (insert-left [1 2]) left next next next next prev show-ip) ; [[1 2 *]]