After writing this answer, I was inspired to try to specify Clojure's destructuring language using spec
:
(require '[clojure.spec :as s])
(s/def ::binding (s/or :sym ::sym :assoc ::assoc :seq ::seq))
(s/def ::sym (s/and simple-symbol? (complement #{'&})))
The sequential destructuring part is easy to spec with a regex (so I'm ignoring it here), but I got stuck at associative destructuring. The most basic case is a map from binding forms to key expressions:
(s/def ::mappings (s/map-of ::binding ::s/any :conform-keys true))
But Clojure provides several special keys as well:
(s/def ::as ::sym)
(s/def ::or ::mappings)
(s/def ::ident-vec (s/coll-of ident? :kind vector?))
(s/def ::keys ::ident-vec)
(s/def ::strs ::ident-vec)
(s/def ::syms ::ident-vec)
(s/def ::opts (s/keys :opt-un [::as ::or ::keys ::strs ::syms]))
How can I create an ::assoc
spec for maps that could be created by merging together a map that conforms to ::mappings
and a map that conforms to ::opts
? I know that there's merge
:
(s/def ::assoc (s/merge ::opts ::mappings))
But this doesn't work, because merge
is basically an analogue of and
. I'm looking for something that's analogous to or
, but for maps.