Skip to content

Instantly share code, notes, and snippets.

@robert-stuttaford
Last active June 16, 2016 14:03
Show Gist options
  • Save robert-stuttaford/e1bc7bfbcdf62020277dda1a277394ca to your computer and use it in GitHub Desktop.
Save robert-stuttaford/e1bc7bfbcdf62020277dda1a277394ca to your computer and use it in GitHub Desktop.
Clojure core.spec question: composing s/keys with s/and
;; using Clojure 1.9-alpha7
(ns keys-with-and-question
(:require [clojure.spec :as s]
[clojure.spec.gen :as gen]))
(s/def ::id string?)
(s/def ::active? boolean?)
(s/def ::extra int?)
(s/def ::base (s/keys :req-un [::id] :opt-un [::active?]))
(s/def ::base-with-extra (s/keys :req-un [::id ::extra] :opt-un [::active?]))
(s/def ::base-with-extra-with-and (s/and ::base (s/keys :req-un [::extra])))
(comment
(s/conform ::base {:id "1" :active? true})
;; {:id "1", :active? true}
(gen/generate (s/gen ::base))
;; {:id "eXhlTPP6FvBCF", :active? true}
(s/conform ::base-with-extra {:id "1" :active? true :extra 123})
;; {:id "1", :active? true, :extra 123}
(gen/generate (s/gen ::base-with-extra))
;; {:id "6XGsmcQwui2hz7Tw10960E7Kw", :extra -13542}
;; {:id "BP6S79", :extra -5305490, :active? true}
(s/conform ::base-with-extra-with-and {:id "1" :active? true :extra 123})
;; {:id "1", :active? true, :extra 123}
;; but...
(gen/generate (s/gen ::base-with-extra-with-and))
;; Throws ex-info: "Couldn't satisfy such-that predicate after 100 tries."
;; My questions:
;; - Is composing `s/keys` with `s/and` offically supported? It certainly appears to validate and conform!
;; - If it is, how would we write a generator for it?
)
@puredanger
Copy link

Yes, composing s/keys with s/and is very much supported. The key here is knowing how s/and generators work (this is covered in the guide btw) - they generate based on their first predicate, then filter based on subsequent predicates. So in ::base-with-extra-with-and it will generate maps based on ::base, then try to find instances that match (s/keys :req-un [::extra]), and this will never be satisfied.

Depending on what your actual need is, I could recommend a variety of answers. You already have a generator for this with (s/gen ::base-with-extra).

@robert-stuttaford
Copy link
Author

Ok, great, thank you -- I had not realised the guide covers this.

So, the case I'm trying to solve for, is spec reuse. The with-extra case is actually one of many; I don't want to duplicate all of base into each of the many extra cases -- I want to reuse base with s/and, as I have done there. And that works, when validating and conforming, which is awesome!

What I'd like to know is how to write a generator for it, because that'd be even more awesome.

Given what you've just answered, It seems like I may have to write a generator of my own that merges the base spec with the extra spec. I'm guess what I'm hoping is I can do this with out redeclaring the nature of the specs just for the generator.

Thanks, @puredanger!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment