Rovanion
Rovanion

Reputation: 4582

Is it possible to resolve a symbol to its value before passing it to macro in Clojure?

Say that I have the following array of keywords

(def keys [::description ::url ::mailing-list])

which I want to reuse in two specs; one for defining a map and one for defining optional arguments to a function.

(require '[clojure.spec :as spec])

(spec/def ::project-map
      (spec/keys :opt-un keys))

(spec/def ::project-args
      (spec/keys* :opt-un keys))

The problem is then that the keys and keys* get passed the quoted symbol 'keys and not the resolved value held in the variable it's referring to.

So my question is: Is it possible to resolve the value of keys at read time like the common lisp #. reader macro, or does the macro have to be redefined to resolve the symbol if it gets a symbol instead of a list literal?

Upvotes: 4

Views: 539

Answers (4)

TankorSmash
TankorSmash

Reputation: 12747

I had a similar issue where I was trying to register a spec dynamically. I had trouble because I wasn't passing in literal keywords, so the solution was to eval the macro's args and then refer to those:

(def my-key ::this-is-a-test-key)

(defmacro define-spec [spec-key validator]
  (let [local-key (eval spec-key)]
    `(s/def ~local-key ~validator)))

(define-spec my-key int?)

;;testing it
(s/valid? my-key 123)
(s/valid? my-key "ASDASD")

It's also worth noting that in cljs, calling define-spec will return nils because cljs doesn't support macros.

Upvotes: 0

Alex Gherega
Alex Gherega

Reputation: 1

Or you could just do:

(spec/def ::project-map (spec/keys ::opt-un ~keys))

Might need to use qualitified namespaces (e.g. ::opt-un)

spec/def

is already a macro ;)

Upvotes: -1

l0st3d
l0st3d

Reputation: 2968

You could also wrap them in another macro, since macros are responsible for how their arguments are evaluated.

(defmacro def-both [name name* keys]
  `(do (s/def ~name (s/keys :opt-un ~keys))
       (s/def ~name* (s/keys* :opt-un ~keys))))

(user/def-both ::project-map ::project-args [::description ::url ::mailing-list])

Upvotes: 1

Rovanion
Rovanion

Reputation: 4582

You can construct a half-icky solution with eval:

(spec/def ::project-map
  (eval `(spec/keys :opt-un ~keys)))

(spec/def ::project-args
  (eval `(spec/keys* :opt-un ~keys)))

This was suggested by mpened on the clojurians slack.

Upvotes: 1

Related Questions