Fork me on GitHub
#clojure-spec
<
2018-10-23
>
favila00:10:18

Other use case

favila00:10:25

But it’s the same basic problem

favila00:10:02

There’s a general structure and spec, but also more specific specs which must nevertheless maintain the structure

favila00:10:48

Using predicates all the time became unbearable

j0ni13:10:25

Hey folks, I'm having a tough time trying to figure out how multi-specs interact with unqualified keys - here's some example code which is a trivial version of what I'm trying to achieve:

(def mode? #{:dog :cat})
(s/def ::cat string?)
(s/def ::dog string?)
(s/def ::mode mode?)
(s/def ::dog-o (s/keys :req-un [::mode
                                ::dog]))
(s/def ::cat-o (s/keys :req-un [::mode
                                ::cat]))
(defmulti animode ::mode)
(defmethod animode :dog [_] ::dog-o)
(defmethod animode :cat [_] ::cat-o)
(s/def ::animal (s/multi-spec animode ::mode))
then some repl:
user> (s/explain ::animal {:cat "edward" :mode :cat})
val: {:cat "edward", :mode :cat} fails spec: :user/animal at: [nil] predicate: animode,  no method
=> nil
user> (s/explain ::animal {:cat "edward" ::mode :cat})
val: {:cat "edward", :user/mode :cat} fails spec: :user/cat-o at: [:cat] predicate: (contains? % :mode)
=> nil
any guidance as to how I square this apparent circle?

seancorfield14:10:40

@j0ni Your defmulti and s/multi-spec should have :mode, not ::mode. You want a function that operates on your data and returns the discriminant. That's the keyword :mode, not the spec ::mode.

seancorfield14:10:46

(def mode? #{:dog :cat})
(s/def ::cat string?)
(s/def ::dog string?)
(s/def ::mode mode?)
(s/def ::dog-o (s/keys :req-un [::mode
                                ::dog]))
(s/def ::cat-o (s/keys :req-un [::mode
                                ::cat]))
(defmulti animode :mode)
(defmethod animode :dog [_] ::dog-o)
(defmethod animode :cat [_] ::cat-o)
(s/def ::animal (s/multi-spec animode :mode))
then
user=>  (s/explain ::animal {:cat "edward" :mode :cat})
Success!
nil

j0ni14:10:04

wow, thanks @seancorfield! I had been through this permutation, but apparently evaluating the ns is insufficient, I needed to reload the namespace completely

seancorfield14:10:11

A trick with defmulti is to put (def animode nil) above it and then it should work with just a re-eval of the ns.

4
j0ni14:10:26

ooh nice 🙂

j0ni14:10:57

well, useful, if actually a little gross 🙂 but I'll take it

adamfrey15:10:05

I was surprised by this behavior in spec. Is there a reason that I shouldn't expect this to work?

(gen/generate (s/gen #{:works})) => :works
  (gen/generate (s/gen #{nil})) => clojure.lang.ExceptionInfo    Couldn't satisfy such-that predicate after 100 tries.
I know I can do (gen/generate (s/gen nil?)) to get around this, but I found this while writing some code for handling generic generator overrides

adamfrey15:10:33

I guess I can take generators out of the equation and get to the crux of the issue:

(s/valid? #{nil} nil) => false
(s/valid? #{false} false) => false
the spec just calls the set as a function and (#{nil} nil) => nil in clojure and spec is looking for the function truthiness. The only way to make this work would be to change the way spec deals with sets as specs where spec could call them with (contains? #{nil} nil) instead. But that sort of special casing might be undesirable.

bmaddy16:10:47

I'm trying to come up with a spec for nested associative structures:

(s/def ::nested-associative
  (s/or :coll (s/coll-of (s/or :branch ::nested-associative
                               :leaf string?)
                         :min-count 0
                         :max-count 1
                         :gen-max 1)
        :map (s/map-of keyword? (s/or :branch ::nested-associative
                                      :leaf string?)
                       :min-count 0
                       :max-count 1
                       :gen-max 1)))

;; in the repl:
> (gen/sample (s/gen ::nested-associative) 100)
 clojure.lang.ExceptionInfo: Unable to construct gen at: [:map 1 :branch :map 1 :branch :coll :branch :coll :branch] for: :user/nested-associative
Does anyone know why this would be crashing randomly? Alternatively, is there maybe a better way to do this?

bmaddy16:10:35

Interesting, it works if I just generate the s/coll-of form. It must be something to do with s/or.