Question: how can you define or alter specs programmatically? I have my data model defined in an edn file and like to generate specs based on that => values
(s/describe 'user/k)
Exception Unable to resolve spec: :my.ns/foo  clojure.spec.alpha/reg-resolve! (alpha.clj:69)
(s/describe :my.ns/foo)
=> user/k
  (s/def k values))
      values integer?]
(let [k :my.ns/foo

Asked By
Asked At
2018-01-12 15:33:07

Found 15 possible answers.

User Answered At Possible Answer
mpenet 2018-01-12 15:33:43 with eval, or a macro
stijn 2018-01-12 15:34:01 ok
kenny 2018-01-12 23:19:25 I'm confused why the first s/keys call does not work and the second one does:
(def my-keys [::a])
=> false { :: a "a"}) (s/valid? (s/keys :req (conj my-keys :: b)) => true :: b "b"}) { :: a "a" And it's not that it just compiles. The conj is actually evaluated:
(s/valid? (s/keys :req (conj my-keys ::b))
#object [clojure.spec.alpha$map_spec_impl$reify__1931 0x2d5c83e "clojure.spec.alpha$map_spec_impl$reify__1931@2d5c83e"] => (s/keys :req (conj my-keys :: b)) java.lang.IllegalArgumentException: Don't know how to create ISeq from: clojure.lang.Symbol (s/keys :req my-keys) => #'user/my-keys
gklijs 2018-01-12 23:58:16 It's because in the first one you suply a symbol, while in the second a list. Not really sure why though.
kenny 2018-01-13 00:23:33 The asymmetry does not make sense to me. Either both should throw or both be allowed.
seancorfield 2018-01-13 02:11:26 The (s/keys :req (conj my-keys ::b)) is treated as (s/keys :req [conj my-keys ::b]) so it requires ::b but the other two elements are symbols so s/keys probably doesn't handle them as expected (it certainly _does not_ evaluate them).
        req-un-specs (filterv keyword? (flatten req-un))
Ah, looking at the source of `s/keys` it filters out anything that isn't a keyword: 
req-keys (filterv keyword? (flatten req)) I expect that deep within the implementation of s/keys , the (unevaluated) _symbols_ conj and my-keys are processed in such a way that they fail to be treated as keys at all. @kenny Hence, when you supply a map containing ::b it validates and when it doesn't contain ::b it is invalid.
kenny 2018-01-13 02:16:04 Then why is this validated correctly?
(def my-keys [::a])
I always forget the implict checking of keys.
Oh, duh.
=> true :: b "b"}) { :: a 1
(s/valid? (s/keys :req (conj my-keys ::b))
=> false :: b "b"}) { :: a "a" (s/valid? (s/keys :req (conj my-keys :: b)) => :boot.user/a (s/def :: a int?) => #'boot.user/my-keys
seancorfield 2018-01-13 02:17:30 It looks like s/keys tries to treat anything that isn't a keyword in the sequence supplied to :req or :req-un as some sort of predicate but I'm not quite sure I follow all the parts of the macro...
kenny 2018-01-13 02:18:24 Doesn't Spec auto-check any keys in a map against any specs defined for those keys?
 ::b "b"}
Which would explain why 
{ :: a "a"
seancorfield 2018-01-13 02:19:29 Ah, I see what it's doing with those symbols:
  :pred-forms (quote
<@kenny> Did you read the bit where I said `(conj my-keys ::b)` is *not evaluated*?
and when you evaluate `clojure.core/conj` and `my-keys` they evaluate to truthy values (not nil or false).
                 (clojure.core/contains? % :user/b))]),
[%] (clojure.core/fn (clojure.core/fn [%] my-keys) (clojure.core/fn [%] clojure.core/conj) [(clojure.core/fn [%] (clojure.core/map? %))
kenny 2018-01-13 02:20:34 Yes
seancorfield 2018-01-13 02:21:03 It treats that as the key ::b and two predicates (which are "true"). So your spec is equivalent to (s/keys :req [::b])
kenny 2018-01-13 02:21:22 Righttt Is that from a macroexpand?
seancorfield 2018-01-13 02:21:46 Yes, the above :pred-forms snippet is
kenny 2018-01-13 02:22:33 Gotcha. As always, everything has a logical explanation :slightly_smiling_face:

Related Questions