jschoi
jschoi

Reputation: 2068

Clojure macro: getting syntax-quoted/namespace-qualified symbol

Let’s say that I want to define a macro called defsomething such that this:

(defspecial a x)

…expands into this:

(def a (f `a x))

That syntax-quotation `a there is the tricky part. I don’t see how I can attach the current namespace to the symbol properly so that it behaves like the syntax-quote. For instance:

(defmacro defspecial [var-symbol expr]
  `(def ~var-symbol (f '~var-symbol ~expr)))

(defspecial a x)

…expands into:

(def a (f 'a x))

…which is close, but that second a is not namespace-qualified.

I know I could use *ns*:

(defmacro defspecial [var-symbol expr]
  `(def ~var-symbol (f (symbol (name (ns-name *ns*)) (name '~var-symbol)) ~expr)))

…but I don’t like that; not only is it ugly, *ns* is rebindable, so this would be possible:

(binding [*ns* (the-ns 'clojure.core)]
  (defspecial a 5))

Unfortunately, Clojure apparently has no syntax-quote form for ` like it has quote for '. And so, how should I best implement this macro, then?

Upvotes: 1

Views: 1958

Answers (2)

Arne Brasseur
Arne Brasseur

Reputation: 1518

Here's a version that works for Clojure and ClojureScript:

(defn qualify-sym
  [env s]
  (if (:ns env)
    ;; cljs
    (symbol (name (-> env :ns :name)) (name s))

    ;; clj
    (symbol (str *ns*) (str s))))

(defmacro example [name val]
  `(def ~name {:id '~(qualify-sym &env name) :value ~val}))

(example xxx 123)

xxx
;;=>
{:value 123, :id my-ns/xxx}

If you need to also take into consideration aliases and refered vars then this a more complete version:

(defn qualify-sym [env s]
  (if (:ns env)
    ;; cljs
    (if (simple-symbol? s)
      (or (some-> env :ns :uses s name (symbol (name s)))
          (symbol (name (-> env :ns :name)) (name s)))
      (symbol (or (some-> env :ns :requires (get (symbol (namespace s))) name)
                  (namespace s))
              (name s)))

    ;; clj
    (if (simple-symbol? s)
      (or (some-> (ns-refers *ns*) (get s) symbol)
          (symbol (str *ns*) (str s)))
      (let [ns (namespace s)
            n (name s)
            aliases (ns-aliases *ns*)]
        (symbol (or (some-> aliases (get (symbol ns)) ns-name str) ns) n)))))

E.g. if you have (:require [clojure.string :as str]), then qualifying str/join will return clojure.string/join

Upvotes: 2

amalloy
amalloy

Reputation: 91857

Have you actually tried the last example you gave? The compiler's binding of *ns* happens at compile-time, as does the macroexpansion, so the run-time binding you do should have no effect on the behavior of defspecial.

Upvotes: 1

Related Questions