Fork me on GitHub
#clojure-spec
<
2020-11-14
>
r08:11:10

hey all, i’m trying to write a spec for a map using s/keys that may contain keys that are namespaced or unnamespaced:

(s/keys :opt [::foo ::bar ::baz] :opt-un [:foo ::bar ::baz])
alright, that’s all well and good! but now i’d like to reduce the duplication (in my real-life application, the vectors have ~10 items each)
(def ks [::foo ::bar ::baz])
(s/keys :opt ks :opt-un ks)
this doesn’t work, because ks is a symbol within the keys macro. i’ve tried unquoting:
(def ks [::foo ::bar ::baz])
(s/keys :opt ~ks :opt-un ~ks)
but this doesn’t work either—it fails the namespace-qualification assertion within the keys macro. any pointers on how to reuse these keys? thanks 🙂

borkdude09:11:37

That doesn't work indeed. You will need to write macros to accomplish this because s/keys itself is a macro

dvingo14:11:05

@robhanlon I came up with this helper for composing keys:

(defmacro def-domain-map
  "Return s/keys :req for fields supports passing in symbols for keys"
  ([spec required]
   (let [req (eval required)]
     `(s/def ~spec (s/keys :req ~req))))
  ([spec required opt]
   (let [req        (eval required)
         opt        (eval opt)
         global-opt (eval global-keys)]
     `(s/def ~spec (s/keys :req ~req :opt ~(into opt global-opt))))))
It works from clj and cljs:
(>def :work-log/id fuc/id?)
(>def :work-log/description string?)
(>def :work-log/begin tu/date-time?)
(>def :work-log/end tu/date-time?)

(def required-work-log-keys
  [:work-log/id :work-log/description :work-log/begin :work-log/end])
(def optional-work-log-keys [])

(su/def-domain-map ::work-log required-work-log-keys optional-work-log-keys)

dvingo14:11:12

I wanted the required and optional keys to be reused in different parts of the app without needing to maintain two lists of these keys

Alex Miller (Clojure team)15:11:21

at some point you have to ask yourself - is this easier than just saying the keys twice?

r20:11:37

I think I have my answer here—I’ll just repeat the keys. Thank you 🙏