Fork me on GitHub
#clojure-spec
<
2022-10-02
>
FiVo11:10:01

Is there a standard way to reuse specs for conformed values? Do I need write a spec for the "normal" as well as conformed version of the data? As an example:

(s/def ::tuple (s/and vector?
                      (s/cat :first identity :second identity)))

(def tuple (s/conform ::tuple [1 2]))

(defn foo [t] t)

(s/fdef foo :args (s/cat :t ::tuple))

(foo tuple) ; => error
I would like to reuse ::tuple for my funciton spec.

Alex Miller (Clojure team)14:10:21

What’s the error? (and why not use s/tuple in that spec?)

FiVo14:10:39

The ::tuple spec was just an example and maybe it was not clear what I am asking. I am mainly wondering if there is a way to avoid the ::tuple-conformed spec in the following

(s/def ::tuple (s/and vector?
                        (s/cat :first int? :second int?)))

  (def conformed-tuple (s/conform ::tuple [1 2]))
  conformed-tuple
  ;; => {:first 1, :second 2}

  (s/valid? ::tuple conformed-tuple)
  ;; => false

  (s/def ::first int?)
  (s/def ::second int?)
  (s/def ::tuple-conformed (s/keys :req-un [::first ::second]))
  (s/valid? ::tuple-conformed conformed-tuple)
  ;; => true

FiVo14:10:59

So I think the question becomes how do people usually generate both specs for the above case?

Brian Beckman19:10:41

SOLVED! Hello, speccers. I’m really stuck and would be very grateful for advice! I’m writing specs to generate test cases for the back end of a compiler (LPython [sic]; http://lpython.org). The compiler back-end accepts a kind of S-expression syntax (nice!?). TL;DR: I want to generate S-exprs like (const 42 (I 4)) ; :head const, :val 42, :type (I 4) I can easily get A. [const 42 (I 4)] ; error: need round brackets outside B. (const 42 I 4) ; error: need round brackets inside C. (const 42 ((I 4))) ; error: too many brackets inside but not what I want without un-nestable post processing. DETAILS: (I 4) could be other types like (F 16) and (C 32) in actual code, so the :type spec (it seems), must be a s/cat to get the internal round brackets:

(s/def ::ttype  (s/cat :head #{'I}, :bytes #{4}))
(->> ::ttype s/gen gen/generate) ; ~~~> (I 4)
I run into trouble right away trying to spec ::iconst to generate (const 42 (I 4)) A. If I spec ::iconst as a tuple, I get a well-formed expression, but with square brackets outside!:
(s/def ::iconst (s/tuple #{'const} integer? ::ttype))
(->> ::iconst s/gen gen/generate) ; ~~~> [const 42 (I 4)]
B. If I spec ::iconst as a s/cat, the ::ttype field gets spliced instead of appended:
(s/def ::iconst (s/cat :head #{'const} :value integer?
            :type ::ttype))
(->> ::iconst s/gen gen/generate) ; ~~~> (const 42 I 4)
B. If I try a nested s/cat for the ::type field, it’s no help:
(s/def ::iconst (s/cat :head #{'const} :value integer?
            :type (s/cat :singleton ::ttype)))
 (->> ::iconst s/gen gen/generate) ; ~~~> (const 42 I 4)
C. If I try s/coll-of :into () :count 1, I get TOO MANY brackets!
(s/def ::iconst (s/cat :head #{'const} :value integer?
            :type (s/coll-of ::ttype :into () :count 1)))
(->> ::iconst s/gen gen/generate) ; ~~~> (const 42 ((I 4)))
The only solution I’ve been able to see is a post-processing step, but this is EXTERNAL to the spec and doesn’t scale to my realistic recursively nested examples:
(s/def ::iconst (s/tuple #{'const} integer? ::ttype))
(->> ::iconst
   s/gen
   gen/generate
   (apply list)) ; ~~~> (const 42 (I 4))
Z. Even s/with-gen takes me back to the future:
(s/def ::iconst-with-gen
 (s/with-gen ::iconst
  (fn [] (tgen/let [iconst ::iconst]
      (apply list iconst)))))
(->> ::iconst-with-gen s/gen gen/generate) ; ~~~> fails after 100 tries
I’m really stuck and would be most grateful for ideas!

phronmophobic19:10:02

I think wrapping the ::type spec with s/spec will do what you want:

(s/def ::ttype (s/spec (s/cat :head #{'I}, :bytes #{4})))
(s/def ::iconst (s/cat :head #{'const} :value integer?
                       :type ::ttype))
(->> ::iconst s/gen gen/generate) ;; (const -241544 (I 4))

🙏 1
Brian Beckman19:10:17

SOLVED! thanks!

🎉 1
Brian Beckman19:10:18

Is there a general rule-of-thumb I can add to my “intuition-bank” about this? I would have thought the s/spec wrapper to be redundant!

Brian Beckman20:10:10

Maybe I should just think of it as making a “nestable” from a “spliceable” spec in s/cat

phronmophobic21:10:19

yea, it's not totally obvious. it is mentioned in the docs, https://clojure.github.io/spec.alpha/clojure.spec.alpha-api.html#clojure.spec.alpha/spec >

Can also be passed the result of one of the regex ops -
> cat, alt, *, +, ?, in which case it will return a regex-conforming
> spec, useful when nesting an independent regex.
https://clojure.org/about/spec#_defining_specs > spec should only be needed when you want to override a generator or to specify that a nested regex starts anew, vs being included in the same pattern.

👍 1
Brian Beckman22:10:48

You’re very generous! The doc is not emphatic about it, but it is clear. I found this SO that helped me quite a bit, too https://stackoverflow.com/questions/37583708/clojure-spec-alt-vs-or-for-sequence-spec