Fork me on GitHub
#clojure-spec
<
2020-01-02
>
Eddie03:01:17

I know that you can pull the :ret and :arg spec from a function spec.

(def my-fn-spec
    (s/fspec :args (s/cat :x int?)
             :ret int?))

(s/valid? (:args my-fn-spec) [5])  ;; true
(s/valid? (:ret my-fn-spec) 5)     ;; true
Is there a way to decompose/query other kinds of specs? For example, I would like to do something like the following to get the spec for the individual fn argument named x.
(:x (:args my-fn-spec))
;; or maybe
(first (:args my-fn-spec))

seancorfield03:01:20

I think the TL;DR is not easily with Spec 1 @erp12 but Spec 2 offers more facilities for taking specs apart and programmatically building them.

seancorfield03:01:21

In Spec 1, you can get the form of a Spec and break it apart, but it isn't easy to turn that back into Spec objects that you can use tho'...

Eddie03:01:39

@seancorfield Good to know, thank you! Based on that, would you agree that currently the best option would be to keep the sub-specs in a map and use some utility functions to "materialize" real specs from them. Just spitballin' here but something like ...

(s/def ::spec (s/spec s/spec?))

; Deconstructed Function Spec
(s/def ::arg-specs (s/coll-of ::spec))
(s/def ::ret-spec ::spec)
(s/def ::d-fn-spec (s/keys :req [::arg-specs ::ret-spec]))

; Deconstructed Collection Spec
(s/def ::coll-kind ::spec)
(s/def ::element-spec ::spec)
(s/def ::d-coll-spec (s/keys :req [::coll-kind ::element-spec]))

; Deconstructed Map Spec
(s/def ::key-spec ::spec)
(s/def ::value-spec ::spec)
(s/def ::d-map-spec (s/keys :req [::key-spec ::value-spec]))

(defn construct-spec 
  [m] 
  ...)

Eddie03:01:37

Or do you know of any other pattens followed by the community for stuff like this?

seancorfield03:01:37

I don't really understand what problem you are trying to solve here... It doesn't look like the sort of thing I've seen anyone trying to do with Spec.

seancorfield04:01:20

Have you looked at Spec 2? That's much more amenable to programmatic manipulation of specs...

rafael20:01:16

Hi. I'm struggling with generating data from a simple (s/schema) use case.

(s/def ::x int?)
  (s/def ::baz (s/schema [::x]))
  (s/def ::bar ::baz)
  (s/def ::foo (s/schema [::bar]))
  (gen/sample (s/gen (s/spec ::foo)))

rafael20:01:57

The call to (gen/sample) throws a No implementation of method: :conform* of protocol: #'clojure.alpha.spec.protocols/Spec found for class: clojure.lang.Keyword exception.

rafael20:01:40

While a straightforward translation to s/keys seems to work fine:

(s/def ::x int?)
  (s/def ::baz (s/keys :opt [::x]))
  (s/def ::bar ::baz)
  (s/def ::foo (s/keys :opt [::bar]))

  (gen/sample (s/gen (s/spec ::foo)))
I'm probably getting something wrong in my schema definitions, but I can't figure out the problem.

Alex Miller (Clojure team)20:01:02

code looks fine, prob just a bug

rafael20:01:02

Cool, I'll stick with the (s/keys ..) version for a while.

rafael20:01:12

Changing the :bar definition to

(s/register ::bar (s/resolve-spec ::baz))
appears to work around the issue.

Alex Miller (Clojure team)20:01:06

that makes sense - you're basically copying the spec object rather than relying on resolving through the alias

Alex Miller (Clojure team)20:01:23

I have a pretty good hunch on where that bug is

rafael20:01:18

Awesome, thanks!