Fork me on GitHub

(require '[clojure.spec.alpha :as s])
(require '[clojure.spec.gen.alpha :as gen])
(s/def ::foo (s/keys* :req-un [::a ::b]))
(s/def ::a number?)
(s/def ::b number?)
(s/valid? ::foo [:a 1 :b 2]) ;; true
(gen/generate (s/gen ::foo {::a (fn [] (gen/return 1))})) ;;=> (:a -15164 :b 24.0)
any reason I can’t override a key generator in a keys* spec?


@alexmiller JIRA here: I could take a look myself if you’re still accepting patches for spec1.


I think it’s a more general problem:

   (s/gen (s/with-gen ::foo
            (fn []
              (s/gen (s/cat :key-a #{:a} :val-a ::a :key-b #{:b} :val-b ::b))))
          {::a (fn [] (gen/return 1))})) ;;=> (:a -1 :b "OXi2")
Overriding the generator on a spec that’s made with with-gen has no effect apparently?

Alex Miller (Clojure team)14:02:19

Oh, that’s true based on the impl for sure


I finally watched the Maybe Not talk and have some comments. Agree totally based on our history of modeling a biomedical research domain that schema/select is a great idea. A context is needed in order to know the required/optionality. For us information gets added to entities over time, what is an optional field in one stage in the pipeline, may be required in a future stage. However, I think something additional is missing. Our predicates also depend on the context. The allowable values for a key depend on the context, so we want to attach narrowing predicates at that 'select' time, not just the required/optionality part. Hopefully a simple example: if we define a Car entity, with a make, the make is validated against a CV list that has lots of makes in it. However, if we are working within a particular dealership context - a dealership which only sells Jeeps and Land Rovers, we want to add a new predicate at that select time which not only requires the make be present, but that it also be one of those two values, and not just any kind of make. Not only is the shape gaining requirements in some contexts, the allowable values are as well. The predicates added at select time are always and- they reduce the set of allowable values and cannot make it more general - thus, there is no breaking change to the original s/def contract for the key. Rich was close to this idea during his talk and applied it to shapes, but not to predicates. Hopefully, I've described all this well enough, and I'm not missing something obvious, but my Google/Slack-fu didn't turn up anything relevant on this. Thanks.


@U4NQYBCU8 I trully believe in this idea of information getting adding over selections over time, maybe you will be interesting to check a library that I maintain that pushes this idea futher, and provides a system where by defining inputs and outputs as maps (with schema/select like definitions), the system can transition from some input to some output, given there is some available path in the system. this is the lib: and if you like you can see I talk I gave about it (also in this conj :))


There’s no documentation on this project yet but this is the intended usage of:


You can get an idea how to use it by reading the tests (including spec generators) and source code.


The strong point of this lib is very user-friendly validation failure error messages.

Alex Miller (Clojure team)21:02:38

you can s/and additional predicates any time you like


But that changes things in a global context... an earlier stage context doesn't want to 'see' the additional s/and for that def.

Alex Miller (Clojure team)23:02:33

I’m saying you can s/and additions predicates into a new spec, either at the point of use or registered


But isn't the point of schema/select being able to reuse the same schema in different select contexts, so that registering new specs is not necessary. e.g. I want to use the same schema in two contexts 1) makes allow only land rovers and jeeps and 2) makes allow fords and chryslers ... what I'm doing there is wanting to add predicates at the select stage. Am I missing something here?


It's not always a linear accretion and narrowing of information - there are forks where one part of the system has different rules for validating cars than another part (e.g. one dealership has different rules than another) but the schema for a car is always the same.


Would something like this work in spec2?

(s/def ::model string?)
(s/def ::car (s/keys [::model]))

;; Would "select" the ::model key AND apply additional rules
(s/select ::car [(s/and ::model #(is-made-by-jeep? %))])

(s/select ::car [(s/and ::model #(is-made-by-ford? %))])


(pseudo code of course)


I'm not sure I understand when you say "you can s/and additional predicates any time you like". I want to add additional predicates at "selection-time", like above

Alex Miller (Clojure team)19:02:44

I get what you're asking

Alex Miller (Clojure team)19:02:00

I don't have an answer for you right now


No problem! Was just spit-balling answers for @U4NQYBCU8


Thanks. Just want to be understood and acknowledged, no answers needed... I think you get it. @UFQAPAUU8 example is approx. the thing we want. However, another thing to make clear is that selecting for required/optionality is orthogonal to adding predicates to keys on the schema within a context. A key may still be optional in a spec context - but if the key/value happens to be there, I want to add predicates to that key's spec. Some psuedo-stuff :

(s/def ::id int?)
(s/def ::make string?)
(s/def ::model string?)
(s/def ::color string?)

(s/def ::car (s/schema [[::id ::make ::model ::color]]))

(favorite-car car) needs ::make, ::model
(favorite-car) checks (::make is 'jeep') and (::model is 'wrangler')
(favorite-car) checks ::color is 'red' or 'black' 
For favorite-car, make and model are required, color is still optional. Predicates in the context are added to both required and optional fields to narrow the specification.