Fork me on GitHub

What is the rational for s/cat returning a map when conformed?


I suppose there is no way to conform a sequence and get a sequence rather than a map?


I know you can use coll-of but that doesn’t work if you want a sequence rather than a collection


you can make it non conforming


the rationale is that s/cat starts a regex spec, and regexes have alternates, so you need to give everything names to know which part of the regex it matched


there's a non-documented not currently supported function that makes a spec nonconforming


are there any docs/guides etc on conforming / conformers and tweaking output etc? I understand roughly how they work, but am curious about how to apply them in practice. What it’s ok to use them for, what it’s not a good idea to use them for etc…

Alex Miller (Clojure team)18:11:00

conformers exist primarily to build custom composite spec types (s/keys* for example)


interesting hadn’t seen s/keys* before

Alex Miller (Clojure team)19:11:46

s/nilable was originally written this way too although I ended up rewriting a custom impl for better performance

Alex Miller (Clojure team)18:11:29

generally I would say you should not use them for data coercion or tweaking output


I have a case where I try to spec a HOF but I can not get check to satisfy. Here's a toy example of the problem:

(def uuid-regex #"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")

(s/def ::name string?)
(s/def ::age pos-int?)
(s/def ::id (s/and string? #(re-matches uuid-regex %)))
(s/def ::person (s/keys :req-un [::name ::age ::id]))

(s/fdef do-stuff
        :args (s/cat :person ::person
                     :lookup (s/fspec :args (s/cat :p ::person) :ret int?)))

(defn do-stuff [person lookup-fn]
  (let [result (lookup-fn person)]

;; works
(s/exercise ::person 1 {::id (fn [] (gen/fmap str (gen/uuid)))})

;; do not work
(s/exercise `do-stuff 1 {::id (fn [] (gen/fmap str (gen/uuid)))})

;; do not work
(stest/check `do-stuff {:gen {::id (fn [] (gen/fmap str (gen/uuid)))}})
I am also open to that there are better ways to run check on HOFs that I am missing

Alex Miller (Clojure team)18:11:40

you’ll need to supply a generator on s/fspec

Alex Miller (Clojure team)18:11:52

the thing that’s breaking down is really the gen of ::id I think


is there anyway to define an override? For example if the fspec was provided by a lib?

Alex Miller (Clojure team)18:11:44

what you’re doing should be working - I think this is a bug

Alex Miller (Clojure team)18:11:30

(s/exercise (s/fspec :args (s/cat :p ::person) :ret int?) 10 {::id #(gen/fmap str (gen/uuid))}) is minimally sufficient


Good to know, then I can stop scratching my head. Yes, that example yields the same result

Alex Miller (Clojure team)18:11:57

yeah, I see the bug in the code. can’t say I know how to fix it though.

Alex Miller (Clojure team)18:11:04

when the fspec is conformed by exercise (or by check), it “checks” the generated function spec, but that conform does not have access to the gen overrides so it gens without them.


I see, that explains the observed behavior


should I file a ticket? Have not done so before but I am willing to try if that would be helpful


thanks, will follow the activity, and thanks for your help and explanation of the problem

Alex Miller (Clojure team)19:11:33

it’s kind of a deep problem - we’ve got other tickets that are related but I never took the time to really get it.


I would be happy to help, but it sounds like its above my knowledge of the code base if it is a deep problem, not a novice ticket


@rickmoynihan Where I've found conformers useful is dealing with input data that is all strings, but is expected to conform to numeric, boolean, date, etc types.


@seancorfield: yeah I understand the input coercion at boundaries case


That's pretty much the only place I'd "recommend" them, having worked with spec in production since it first appeared.


ok thanks, that’s useful. I just remember rich saying that conformed values are basically the parsed/labelled output you want; and it’s very close to a shape I want for a specific case… thinking it’s better to conform then post-process, rather than use the conformers to do it.


but it occurred to me that if I used conformers then I’d really need to write unformers too, and my spec output would no longer be spec output… so what you’re saying seems to agree my gut feeling


so one thing I’d quite like (for myself) is to write a variant of s/keys and s/def that uses URIs as keys instead of clojure keywords. I work with RDF, so mapping to keywords just to spec something feels somewhat redundant, when the URI straight out of the database is basically the same thing so I’d like to basically save a redundant mapping and write: (def rdfs:label (URI. "http://,,,/label")) (rdf/def rdfs:label string?) (rdf/keys :req [rdfs:label]) And have it do the same thing as keys. Is there an easy way to do this? Thinking I need to extend s/Spec/`s/Specize` to URI and then rewrite s/keys? s/keys looks pretty hairy, is there any easy way?

Alex Miller (Clojure team)19:11:58

I think this is roughly how you would do this, yes, and it would not be super easy. Also, I expect some of that plumbing to possibly change soon.


I’m assuming there are no plans/proposals in the pipeline to let you plugin and you use arbitrary values as keys/keywords?


or am I thinking the thoughts of a madman?


I think what rich says about the design influence of RDF on clojure and spec is very clear; in many ways they’re almost identical. I’d basically like to reduce the friction of working in clojure with RDF… Also compare SHACL/SHEX to spec, they’re almost just different syntaxes for the same abstract model.


ok, thanks for the comments bfabry and alexmiller, I’ll avoid using spec for transforms.