2

I find myself writing a lot of specs like this:

(s/def ::name string?) (s/def ::logUri string?) (s/def ::subnet (s/and string? #(> (count %) 5))) (s/def ::instanceType string?) ... (s/def ::key (s/and string? #(> (count %) 5))) (s/def ::instanceCount string?) (s/def ::bidPct string?) 

I.e. Lots of s/and and s/def. This seems like a waste. So I decided to write a macro that did this for me. Something like:

(defmacro and-spec [validations] (doseq [[keyname & funcs] validations] `(s/def ~keyname (s/and ~@funcs)))) 

So I would be able to do something like:

(and-spec [[::name1 [string?]] [::name2 [string? #(> (count %) 5)]]]) 

And this would just do all my s/def stuff for me. Unfortunately, the above macro doesn't work, but I'm not sure why.

(s/valid? ::name1 "asdf") Execution error at emr-cli.utils/eval6038 (form-init17784784591561795514.clj:1). Unable to resolve spec: :emr-cli.utils/name1 

Smaller versions of this work:

(defmacro small-and-spec-works [key-name validations] `(s/def ~key-name (s/and ~@validations))) => #'emr-cli.utils/tsmall (and-spec-small-works ::mykey [string?]) => :emr-cli.utils/mykey (s/valid? ::mykey "asdf") => true 

But the second I introduce a let binding things start getting weird:

(defmacro small-and-spec [validation] (let [[key-name & valids] validation] `(s/def ~key-name (s/and ~@valids)))) => #'emr-cli.utils/small-and-spec (small-and-spec [::mykey2 [string?]]) => :emr-cli.utils/mykey2 (s/valid? ::mykey2 "asdf") Execution error (IllegalArgumentException) at emr-cli.utils/eval6012 (form-init17784784591561795514.clj:1). Key must be integer 
  1. How can I make the doseq macro work?
  2. What is going wrong with the small-and-spec that creates the Key must be integer error?

1 Answer 1

5
(defmacro and-spec [defs] `(do ~@(map (fn [[name rest]] `(s/def ~name (s/and ~@rest))) defs))) 

doseq is for side effects. It always returns nil.

Sign up to request clarification or add additional context in comments.

3 Comments

Yes, the "side effect" in question is the definition of multiple in-scope specs. I don't want the macro to return a list of defs.
"I don't want the macro to return a list of defs." is actually what you want though, in a do
Specifically because just creating a source code list does not cause it to be evaluated. The "contract" of a macro is that any code it returns will be evaluated. Code it produces as a side effect and then throws away is, naturally, thrown away.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.