This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-11-16
Channels
- # announcements (62)
- # babashka (12)
- # babashka-sci-dev (73)
- # beginners (16)
- # biff (10)
- # calva (65)
- # cider (13)
- # clerk (8)
- # clojure (31)
- # clojure-europe (16)
- # clojure-nl (1)
- # clojure-norway (19)
- # clojure-spec (24)
- # clojure-uk (5)
- # clojuredesign-podcast (18)
- # clojurescript (18)
- # dev-tooling (2)
- # emacs (30)
- # etaoin (4)
- # gratitude (3)
- # hyperfiddle (20)
- # integrant (2)
- # jobs (4)
- # kaocha (7)
- # malli (1)
- # observability (11)
- # off-topic (11)
- # pathom (12)
- # podcasts-discuss (7)
- # portal (12)
- # quil (3)
- # re-frame (6)
- # releases (1)
- # sql (22)
- # squint (5)
- # testing (79)
- # tools-deps (3)
- # xtdb (20)
I’m trying to spec a map which I thought was homogeneous (i.e. key type matching a spec -> value type matching a spec), so I used map-of. Now it turns out that these maps can occasionally have other entries where the key is a constant keyword and then the value is something different to the other value type. I can’t figure out the best way to spec this - any advice?
it's a bit cumbersome but you can spec the map as a collection of tuple (entry) types
https://www.cognitect.com/blog/2017/1/3/spec-destructuring is a complicated example of this
I see, so I’d define two s/tuple types, and then a coll-of :kind map? using s/or over the two types?
(s/def ::map-bindings
(s/every (s/or :mb ::map-binding
:nsk ::ns-keys
:msb (s/tuple #{:as :or :keys :syms :strs} any?)) :into {}))
(s/def ::map-special-binding
(s/keys :opt-un [::as ::or ::keys ::syms ::strs]))
(s/def ::map-binding-form (s/merge ::map-bindings ::map-special-binding))
is kind of the crux of that - an s/merge of special keys via s/every with an s/keysOk, thanks for the pointer, I think I’ll need some time to digest that but I can figure it out from there.
that example is probably more complicated than what you need but that's the main idea
not successfully unless the map-of uses keyword keys
it's merging the requirements of the two specs so any data has to satisfy both
note that the tuple side (s/every of s/tuple) has an :msb branch that is effectively matching all of the known kws with any?
and then the s/keys side is s/opt so it's just ignoring anything else
So when I do an s/merge, every entry has to match both sides of the merge? I’m not sure I understood that.
you are merging the requirements of the spec
so, it's kind of like s/and, but sub specs are effectively checked in parallel whereas s/and flows data through and checks sub specs serially
this also has implications for s/conform but you may not care about that
Here’s my little test case:
(s/def ::normal-key string?)
(s/def ::normal-val string?)
(s/def ::constant (s/map-of ::normal-key ::normal-val))
(s/def ::foo (s/coll-of string? :kind vector?))
(s/def ::special-case (s/keys :req-un [::foo]))
(s/conform ::constant {"foo" "bar"})
(s/conform ::special-case {:foo ["test"]})
(s/def ::mixed (s/every (s/or :constant (s/tuple ::normal-key ::normal-val)
:outlier (s/tuple #{:foo} ::foo))))
(s/def ::end-result (s/merge ::mixed ::special-case))
(s/conform ::end-result {"foo" "bar"})
(s/conform ::end-result {:foo ["test"]}))
So I started with a map of string to string using ::constant
. Then I realised I need ::special-case
too. However my ::end-result
can’t conform {"foo" "bar"}
(s/explain ::end-result {“foo” “bar”}) {“foo” “bar”} - failed: (contains? % :foo) spec: :cursive.extensions.specs/special-case
req-un has to be opt-un there
as string/string entries won't match that