This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-10-02
Channels
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.What’s the error? (and why not use s/tuple in that spec?)
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
You actually kind of answered my question here https://stackoverflow.com/questions/41686718/specs-for-conformed-specs-asts
So I think the question becomes how do people usually generate both specs for the above case?
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!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))
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!
Maybe I should just think of it as making a “nestable” from a “spliceable” spec in s/cat
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.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