A quasiquote for Clojure?

677 views
Skip to first unread message

Meikel Brandmeyer

unread,
Dec 17, 2008, 4:30:42 AM12/17/08
to Clojure
Hi,

I'd like to propose/put up for discussion a change to the unquote
handling.
I basically ran into the following problem:

I'd like to embed a DSL into my system. Using macros it should be
possible
to define a language, which looks like usual Clojure. Simply quote the
form
and pass it to a function, which knows how to handle the commands.

However I hit a roadblock. Coming from Scheme, I tried the following
approach.

A hypothetical SQL embedding:

(def x 5)
(sql col1 from table1 where col2 = ~x)

Or a hypothetical cljsh (inspired by scsh):

(def *cmd* "/usr/bin/command")
(run (~*cmd* -option 1 -another argument foo bar))

In Scheme this is done, by quoting the macros argument using the
quasiquote form. In Clojure, however, this style is not possible since
we are
missing quasiquote. Here a short list of examples on how the different
quotations work:

(quote (foo ~x bar))
=> Failure in Clojure.
=> In some Schemes: (foo (unquote x) bar), where , (aka. ~) translates
to some unquote form. It just quotes everything. Also the unquote.

(quasiquote (foo ~x bar))
=> Not available in Clojure.
=> (foo 5 bar) in Scheme

(syntax-quote (foo ~x bar))
=> (assuming ` would have a syntax-quote form as ' does with quote)
(my.ns/foo 5 maybe.some.other.ns/bar)
=> Not available in Scheme, AFAIK.

So as you see, the above style is not possible, since in Clojure you
only
have the choice to be static or resolve all the symbols. If I want to
use
the style anyway, I have to jump through hoops and rewrite the quoted
form in my handler function, be it replacing somehow marked or named
symbols with values from the environment or be it renormalising all
the
symbols. (But what if the user injected a qualified symbol with ~?)

So I came to the question of whether it is possible to include a
similar
quasiquote form, which does not resolve the symbols inside? It would
be a philosophy of "Unquote, what the user wants to be unquoted, and
leave the rest to me. I know how to handle it."

And when were are at it: maybe also provide a syntax-quote, unquote
and unquote-splice forms?

Finally, I skipped the obvious question: Does this make sense at all?
I like this way of including a sub-language. However, is it
"idiomatic"?
(for some definition of "idiomatic")

Maybe there is already a different solution, which I just don't see at
the moment.

Sincerely
Meikel

David Nolen

unread,
Dec 17, 2008, 12:21:56 PM12/17/08
to clo...@googlegroups.com
You can have unresolved symbols within a macro with:

~'symbol

David

Meikel Brandmeyer

unread,
Dec 17, 2008, 12:48:17 PM12/17/08
to clo...@googlegroups.com
Hi,

Am 17.12.2008 um 18:21 schrieb David Nolen:
> You can have unresolved symbols within a macro with:
> ~'symbol

I don't think this is really an alternative.

>> (def x 5)
>> (sql col1 from table1 where col2 = ~x)

(sql `(~'select ~'col1 ~'from ~'table1 ~'where ~'col2 ~'= ~x))

>> (def *cmd* "/usr/bin/command")
>> (run (~*cmd* -option 1 -another argument foo bar))

(run `(~*cmd* ~'-option 1 ~'-another ~'argument ~'foo ~'bar))

Also list with quote is quite verbose:

(sql (list 'select 'col1 'from 'table1 'where 'col2 '= x))

This also doesn't compare in ease of use and elegance to the
Scheme version.

Sincerely
Meikel


Rich Hickey

unread,
Dec 17, 2008, 2:53:49 PM12/17/08
to Clojure
Moving syntax-quote out of the reader might be a big deal. But I think
scenarios like this could be covered if:

~x not in a syntax-quote yielded the form (unquote x) from the reader

unquote would not be defined by Clojure, so still an error if allowed
to get evaluated.

Things like your sql would be macros that handled (unquote x)
internally.

Rich

Chouser

unread,
Dec 17, 2008, 3:25:55 PM12/17/08
to clo...@googlegroups.com
On Wed, Dec 17, 2008 at 2:53 PM, Rich Hickey <richh...@gmail.com> wrote:
>
> Moving syntax-quote out of the reader might be a big deal. But I think
> scenarios like this could be covered if:
>
> ~x not in a syntax-quote yielded the form (unquote x) from the reader
>
> unquote would not be defined by Clojure, so still an error if allowed
> to get evaluated.
>
> Things like your sql would be macros that handled (unquote x)
> internally.

I very much like the sound of this.

+1

--Chouser

James Reeves

unread,
Dec 17, 2008, 6:17:43 PM12/17/08
to Clojure
On Dec 17, 8:25 pm, Chouser <chou...@gmail.com> wrote:
> > ~x not in a syntax-quote yielded the form (unquote x) from the reader
>
> I very much like the sound of this.
>
> +1

I'm for this as well. I've been using keywords as a substitute, but an
unquote would be more intuitive.

- James

Rich Hickey

unread,
Dec 23, 2008, 11:08:50 AM12/23/08
to Clojure
On Wed, Dec 17, 2008 at 2:53 PM, Rich Hickey <richh...@gmail.com> wrote:
>
>
>

SVN 1184 implements this.

Feedback welcome on its utility for macro writers.

Rich

Chouser

unread,
Dec 23, 2008, 3:20:43 PM12/23/08
to clo...@googlegroups.com

Here's an example of one way to use it:

user=> (where (and (> i (- 3 1)) (< i ~(+ 3 1))))
"where i > 3 - 1 and i < 4"
user=> (let [my-name "'chouser'"] (where (and (> id 0) (= name ~my-name))))
"where id > 0 and name = 'chouser'"

This 'where' macro produces something that looks vaguely like a SQL
"where" clause. It accepts s-expressions to translate, but also
allows you to mark sub-expressions with ~ to indicate that they should
be evaluated as regular clojure expresions, and not translated
literally to SQL. If it were more than a toy example, it would use
prepareStatement or similar to get appropriate quoting of clojure
objects, rather than just stuffing them directly in the resulting
string.

Here's the code:

(defmacro where [e]
(let [f (fn f [e]
(if-not (list? e)
[(str e)]
(let [[p & r] e]
(if (= p `unquote)
r
(apply concat (interpose [(str " " p " ")]
(map f r)))))))]
(list* `str "where " (f e))))

--Chouser

Meikel Brandmeyer

unread,
Dec 28, 2008, 12:08:55 PM12/28/08
to clo...@googlegroups.com
Hi,

Am 23.12.2008 um 17:08 schrieb Rich Hickey:

> SVN 1184 implements this.
>
> Feedback welcome on its utility for macro writers.

Many Thanks Rich!

Here is a half-baked quasiquote macro ripped out of the
syntax-quote reader:

(defmacro quasiquote
[form]
(let [unquote? (fn [f] (and (seq? f) (= (first f) `unquote)))
self-eval? (fn [f] (or (keyword? f)
(number? f)
(instance? Character f)
(string? f)))
flatten-map (fn [m] (reduce (fn [r e]
(-> r (conj (key e)) (conj (val
e))))
[] m))
qq (fn qq [form]
(cond
(self-eval? form) form
(unquote? form) (second form)
(symbol? form) (list 'quote form)
(vector? form) (vec (map qq form))
(map? form) (apply hash-map
(map qq (flatten-map
form)))
(set? form) (apply hash-set (map qq
form))
(seq? form) (list* `list (map qq form))
:else (list 'quote form)))]
(qq form)))

With some example application:

Gorilla=> (def abc 5)
#'user/abc
Gorilla=> (let [fgh 6] (quasiquote (+ ~abc ~(+ abc fgh) xyz)))
(+ 5 11 xyz)

Sincerely
Meikel

Jason Wolfe

unread,
Jan 15, 2009, 6:36:50 PM1/15/09
to Clojure
> >unquotewould not be defined by Clojure, so still an error if allowed
> > to get evaluated.
>
> > Things like your sql would be macros that handled (unquotex)
> > internally.
>
> SVN 1184 implements this.
>
> Feedback welcome on its utility for macro writers.
>
> Rich


I like this a lot. Any chance of getting the same treatment for
unquote-splicing?

user> '~x
(clojure.core/unquote x)
user> '~@x
; Exception: can't embed object in code
; Desired output: (clojure.core/unquote-splicing x)

Or am I just doing this wrong?

Thanks!
Jason

Meikel Brandmeyer

unread,
Jan 17, 2009, 5:02:57 AM1/17/09
to clo...@googlegroups.com
Hi Jason,

Am 16.01.2009 um 00:36 schrieb Jason Wolfe:

> I like this a lot. Any chance of getting the same treatment for
> unquote-splicing?
>
> user> '~x
> (clojure.core/unquote x)
> user> '~@x
> ; Exception: can't embed object in code
> ; Desired output: (clojure.core/unquote-splicing x)

That's a good idea. It would nicely complement the other
functions so far. Please find attached a patch.

1:1 user=> '~x
(clojure.core/unquote x)
1:2 user=> '~@x
(clojure.core/unquote-splicing x)

Sincerely
Meikel

unquote-splice.diff

Jason Wolfe

unread,
Jan 17, 2009, 3:23:54 PM1/17/09
to Clojure
> That's a good idea. It would nicely complement the other
> functions so far. Please find attached a patch.
>
> 1:1 user=> '~x
> (clojure.core/unquote x)
> 1:2 user=> '~@x
> (clojure.core/unquote-splicing x)
>
> Sincerely
> Meikel

Sweet, thanks Meikel! Rich, what do you think about integrating this
into the trunk?

-Jason

Rich Hickey

unread,
Jan 18, 2009, 11:41:27 AM1/18/09
to Clojure
Could you please make an issue for this with the patch attached? -
Thanks

Rich
Reply all
Reply to author
Forward
0 new messages