vidi
vidi

Reputation: 2106

How to expand keyword in Clojure macro

I have the following macro:

(defmacro my-macro [k]
  `(do
     (def pair
        [
          k
          ~(symbol (str "-" (name k)))]
      )))

...which expands to:

(macroexpand-1 `(my-macro :n/k))

(do (def user/pair [user/k -k]))

...but instead I would like it to expand to

(do (def user/pair [:n/k -k]))

How can I make the macro keep the keyword and its namespace?

Thanks!

Upvotes: 0

Views: 359

Answers (3)

Nathan Davis
Nathan Davis

Reputation: 5766

You need to escape k from the syntax quote using ~k:

(defmacro my-macro [k]
  `(def ~'pair [~k ~(symbol (str "-" (name k)))]))

I've made a few other changes here as well:

  • Idiomatic formatting. Don't put ( or [ at the end of a line -- and put closing ) and ] on the same line as the expression they close.
  • do is entirely superfluous here.
  • If you want the macro to expand to (def pair ...), then you need to

    1. escape out of the syntax quote (~)
    2. quote the symbol pair (i.e., 'pair)

    Putting this together, you have ~'pair. The reason you have to do this is because, in Clojure, `<symbol> is read as (quote <current-namespace>/foo>), where <current-namespace> stands for the current namespace. But def doesn't take names that are namespaced. Hence the ~' dance.

    (But you probably want to parameterize on pair anyway ... otherwise, it's not very useful to use my-macro more than once per namespace.)

Overall, this seems like a very odd macro. I don't know what you're trying to accomplish, but I would probably take a different approach.

Upvotes: 1

Alan Thompson
Alan Thompson

Reputation: 29958

Revised Answer

There are 2 things a bit confusing about your question & I misread it earlier.

  1. You should use a regular single-quote ' with macroexpand-1, not the back-tic `. The back-tick is normally used only in a macro definition to delineate a piece of "template code".
  2. I just noticed that the arg in the macro definition is k, and the keyword you use in the example is :n/k. These duplicate names will cause confusion.

Let's restate the problem:

(ns clj.demo)
(defmacro my-macro [arg]
  `(do
     (def pair
        [
          arg
          ~(symbol (str "-" (name arg)))]
      )))
(println (macroexpand-1 `(my-macro :n/k)))

;=> (do (def clj.demo/pair [clj.demo/arg -k]))

So we are in the clj.demo namespace, which gets applied to the symbols pair and arg. We need to substitue the argument arg using ~:

(ns clj.demo)
(defmacro my-macro [arg]
  `(do
     (def pair
        [
          ~arg
          ~(symbol (str "-" (name arg)))]
      )))
(println (macroexpand-1 '(my-macro :n/k)))

;=> (do (def clj.demo/pair [:n/k -k]))

Which is what you want.

Upvotes: 1

Arthur Ulfeldt
Arthur Ulfeldt

Reputation: 91554

you can use the namespace and name function to extract the parts you want from the keyword passed in and combine them as required:

user> (defmacro my-macro [k]
        `(do
           (def pair
             [~(keyword (str (namespace k) "/" (name k)))
              ~(symbol (str "-" (name k)))])))

#'user/my-macro
user> (macroexpand-1 `(my-macro :n/k))
(do (def user/pair [:n/k -k]))

Upvotes: 1

Related Questions