Ultimately, you can create DSL in any language.
What makes Clojure / other Lisps particularly unique and well suited for metaprogramming is that they are homoiconic - the language itself is expressed naturally in the data structures of the same language. In Lisp, you effectively write code directly as an AST .
This is surprisingly powerful β meaning that code generation is actually equivalent to creating a relatively simple data structure. And the language provides the ability to generate arbitrary code at compile time using macros. This effectively allows you to "expand the language" to support any specific DSL that you need.
As an example, I recently discovered that I wanted an imperative for
loop in Clojure (apologies for functional programming purifiers, but sometimes you need one ....). Adding this to the language was a 5 liner:
(defmacro for-loop [[sym init check change :as params] & steps] `(loop [~sym ~init value# nil] (if ~check (let [new-value# (do ~@steps )] (recur ~change new-value#)) value#)))
So now I can do:
(for-loop [i 0 (< i 10) (inc i)] (println i)) => < prints numbers from 0..9 >
This is obviously a simple example, but hopefully it will be clear that the ability to generate new language constructs by creating a set of short macros that extend to exactly the code you want makes DSL creation especially easy.
Some reads / links that may interest you:
source share