clojure-spec

eighttrigrams 2022-11-06T11:35:38.841089Z

Hey, I've got a data structure (which I cannot change) like

[{:identifier "abc" :other-key-1 "abc"}
 {:identifier "SELF" :content {}}
 {:identifier "def" :other-key-2 []}]
I would like to do the following: 1. Determine if there is exactly one item in the vector of maps which has the :identifier "SELF". 2. For that item, determine if there exists the key :content 3. Make sure its value is a map (whose properties I also would like to spec) Can anyone think of a good way of doing this in spec or give me pointers of how to think about this?

walterl 2022-11-06T12:34:53.125719Z

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

(def d [{:identifier "abc" :other-key-1 "abc"}
        {:identifier "SELF" :content {}}
        {:identifier "def" :other-key-2 []}])

(defn- self-id? [{:keys [identifier]}] (= "SELF" identifier))

(s/def :self/identifier #{"SELF"})
(s/def :self/content (s/keys)) ; TBD
(s/def ::self (s/keys :req-un [:self/identifier :self/content]))
(s/def ::not-self (s/or :!m (complement map?) :!self (complement self-id?)))

(s/explain-data ::self (first (filter self-id? d)))

(s/def ::d (s/and (s/coll-of map?)
                  (s/cat :before (s/* ::not-self)
                         :self ::self
                         :after (s/* ::not-self))))

(s/explain-data ::d d)
(s/conform ::d d)
; {:before [[:!self {:identifier "abc", :other-key-1 "abc"}]],
;  :self {:identifier "SELF", :content {}},
;  :after [[:!self {:identifier "def", :other-key-2 []}]]}

eighttrigrams 2022-11-06T14:42:08.797869Z

This is fantastic! Thank you so much, @clojurians-slack100

👍 1
walterl 2022-11-06T14:44:43.605019Z

> "...pointers of how to think about this" Thing of it like regex: you want to match 0 or more non-self maps, followed by a self map, followed by 0 or more non-self maps. (An s/not spec would've been handy here.)

eighttrigrams 2022-11-06T14:47:07.744689Z

Yeah, like a regex. It totally makes sense. The docs even stated this, but just didn't come up with this in the moment.

eighttrigrams 2022-11-06T14:47:54.080169Z

> An s/not spec would've been handy here. 🤔 Interesting. I see, for the :not-self ...

walterl 2022-11-06T14:51:08.611889Z

Yeah, I would've liked to do (s/not ::self) rather than having to define a negation of ::self manually, because now they could drift out of sync.

eighttrigrams 2022-11-06T14:54:24.780799Z

Yes. I have an intuition that it should generally be possible to make such a thing. One would need to account for the different, uhm, don't know the name, the different things like s/keys , s/cat and so on. The negation of the s/keys was an s/or with complement. Not in the mood to solve that puzzle right now, but certainly an interesting challenge 😀