Fork me on GitHub

@alexmiller I got the ball moving here: Looking forward to seeing what comes out of this.


I have the following style spec:

(s/def ::ident (s/and vector? (s/cat :ident keyword? :value #(not (coll? %)))))
(s/def ::join-key (s/or :prop keyword? :ident ::ident))
(s/def ::join (s/and (s/map-of ::join-key ::query) #(= (count %) 1)))
(s/def ::union (s/and (s/map-of keyword? ::query) #(> (count %) 1)))

(s/def ::param-expr
  (s/cat :query-expr ::query-expr
         :params map?))

(s/def ::mutation-expr
  (s/or :no-params (s/cat :mutate-key symbol?)
        :with-params (s/cat :mutate-key symbol?
                            :params map?)))

(s/def ::query-expr
  (s/or :prop keyword?
        :ident ::ident
        :mutation-expr ::mutation-expr
        :union ::union
        :join  ::join
        :param-expr ::param-expr))

(s/def ::query
  (s/or :recursion (s/or :depth number?
                         :unbounded #(= % '...))
        :query     (s/and vector?
                          (s/+ ::query-expr))))

(println (s/conform ::query [:one :two :three]))
(println (s/unform ::query (s/conform ::query [:one :two :three])))
Why does the first print give [:query [[:prop :one] [:prop :two] [:prop :three]]] but the second give (:one :two :three) (sequence not a vector)?


If query is spec'ed as being a vector? shouldn't unform also return a vector?


hi there, I’m having some troubles regarding circular refs in my specs. I have a spec defining an office which has employees in the office ns, and I have an employee (in an employee ns), which should have an office. As you cn guess, this results in circular dependencies (or when I don’t require one of the namespaces, it results in Unable to resolve spec. Is there any recommended method to deal with issues like this?

Alex Miller (Clojure team)13:06:00

@jwkoelewijn code would help. What you're describing should be possible


Does anyone know of any ring middleware that pretty prints ex-info‘s that raise spec explain-data? Thinking of something like prone for dev but for spec problems.


prone currently gives me a huge wall of text


Has anyone worked on a partial spec for specs? Or a test.check generator? I’m mostly interested in generating specs rather than conformance or validation

Alex Miller (Clojure team)14:06:50

bbrinck: is about specs for specs. Those also are a step towards spec generators which I’ve also done some more work that’s not public yet.


Excellent news. I’m voting for that one now 😉


I may work on some generators soon. I’m guessing it will be a subset of what you’ve done, but it’ll be public, so I’m happy to share in case it’s useful.

Alex Miller (Clojure team)15:06:38

I’ve in particular worked on the generators in the direction of testing spec itself. Namely, if you have spec specs with working generators, you can generate specs, then use those to generate data, then validate that the generated values are valid according to the generated specs

Alex Miller (Clojure team)15:06:47

the last time I worked on this I mostly convinced myself there was a bug in multi-spec gen for the non-keyword case that was giving me grief, but I never ran it down


I wonder how you spec that s/conform returns :clojure.spec/invalid as a valid result.


@rickmoynihan This isn’t directly applicable, but data-frisk includes some nice-looking spec messages. I wonder if that could be ported.


Do you need to return explain-data or could your raise an ex-info that contains explain (or some other string representation of the errors)?


I’m just wondering if maybe you could solve the issue by formatting the errors at the source. That might not be desirable though.


Can I destructure metadata? For example, if I have a vector [1 2 3] which has meta {:some :meta} -> [:vector [1 2 3] :meta {:some :meta}]. I can't work out how I'd spec this.


@danielstockton with a predicate I guess


@mpenet Tried that but I couldn't get destructuring as above when conforming. My understanding of spec is still fairly superficial.


you can do this with a custom conformer I think


Ok, I'll look into that, thanks.


I don't need the conformed output exactly as above but I need to be able to easily identify the two parts.


(s/conform (s/and vector? (s/conformer #(hash-map :vector % :meta (meta %))))) (with-meta {} {:foo 1}) -> {:vector [] :meta {:foo 1}}. this example conformer is a bit broken but that's the idea for the coercion part


Cool, why do you say it's broken?


it's ok actually, before the edit it was 🙂


Am I right that conformer takes a second argument to unform? This might address the issue I had above with unforming to a seq.


never used unform but yes, it might work


Still curious to know exactly why it unforms to a seq. I noticed for simpler specs (just vector?) it unforms correctly so it must be a side effects of something.


I think that's a bug


Should I be calling it a vector in the conformed output or is it a 'tuple'?


If it is a bug, I can't work out where it comes from.


@danielstockton for that case I think a tuple makes more sense than a cat, a cat is for more regexy stuff


I think it unforms to a seq because you are using a cat, which is for regexy stuff, and I believe it will always conform to a seq


@wilkerlucio Which cat are you referring to?


The only ones I see are deeper in the spec


this one: (s/def ::ident (s/and vector? (s/cat :ident keyword? :value #(not (coll? %)))))


or I'm looking at the wrong place?


I don't think the query I'm unforming has any idents: [:query [[:prop :one] [:prop :two] [:prop :three]]]. The [:prop :one] is the conformed value for a :prop ::query-expr which is just a keyword?


The original query is just [:one :two :three]


I did think it might be related to a cat or similar, somewhere, but I couldn't explain it.


ah, I think I was reading it wrongly, sorry, so it seems something is not aligned between conform and unform?


I mean, perhaps I just misunderstand unform because it doesn't promise to give back exactly what you put it, only undo the destructuring..


I'm just curious exactly what's causing this behaviour.

Alex Miller (Clojure team)14:06:36

it’s unclear to what is not working as you expect from the back chat - can you restate?


Why does the first print give [:query [[:prop :one] [:prop :two] [:prop :three]]] but the second give (:one :two :three) (a lazy sequence, not a vector)?

Alex Miller (Clojure team)15:06:13

looks like this is sufficient repro:

(s/def ::query (s/and vector? (s/+ keyword?)))
(println (s/conform ::query [:one :two :three]))
(println (s/unform ::query (s/conform ::query [:one :two :three])))


Yep thanks, I made it look unnecessarily complicated.

Alex Miller (Clojure team)15:06:18

so in conform, the general approach is to avoid rebuilding a collection if at all possible, so there you just end up with the original vector on conform

Alex Miller (Clojure team)15:06:46

unform of a regex spec however will always (I think) yield a seq

Alex Miller (Clojure team)15:06:17

unform of an s/and walks the preds in reverse


Make sense, so it's the s/+ specifically. And if I wanted unform to yield the same collection type, I'd use a customer conformer with a second argument? I'm trying to use spec to implement a parser and I want to be able to go from query <-> ast. I don't know if this is a good use case for spec or not.

Alex Miller (Clojure team)15:06:20

the broader problem here is: how do I use a regex spec but constrain to vectors and this is a known problem area that Rich and I have discussed at length as it came up a lot in spec’ing the core macros

Alex Miller (Clojure team)15:06:02

there is currently no simple solution that works as you’d like for all of conforming, generation, explain, and unconform (although you can make subsets of those work)

Alex Miller (Clojure team)15:06:58

I’m trying to work out if in this case you could actually somehow supply a custom unform function to the vector? pred as that’s not something I’ve tried

Alex Miller (Clojure team)15:06:32

looks like there is no easy way to do so although there is certainly the potential to do so


Ok, I'll keep my ear to the ground.


Can i recursively override a generator? e.g. pass a function to s/spec's :gen that overrides just the recursive part, and close over such this also overrides the recursive part, making a depth-limited generator?


I've done this from scratch with clojure.test.check.generators, just trying to see if I can reuse most of what spec would have done.

Alex Miller (Clojure team)22:06:35

Recursive generators are already depth limited based on the dynamic var in spec

Alex Miller (Clojure team)22:06:46

In case that solves the problem