clojure-spec

About: http://clojure.org/about/spec Guide: http://clojure.org/guides/spec API: https://clojure.github.io/spec.alpha/clojure.spec.alpha-api.html
ag 2020-01-29T02:55:36.100800Z

I need a spec for a map like {:a :b} where :b has to be a required key, but only when value of :a is not nil. Can someone help me with that?

seancorfield 2020-01-29T03:27:41.103Z

user=> (s/def ::ab (s/and (s/keys :opt-un [::a ::b]) #(or (nil? (:a %)) (contains? % :b))))
:user/ab
user=> (s/valid? ::ab {:x 1})
true
user=> (s/valid? ::ab {:a nil})
true
user=> (s/valid? ::ab {:a 1})
false
user=> (s/valid? ::ab {:a 1 :b 2})
true
@ag how about that?

seancorfield 2020-01-29T03:28:41.103700Z

If :a should be required, use (s/keys :req-un [::a] :opt-un [::b]) I guess.

ag 2020-01-29T03:33:18.104900Z

ah, I was playing around, come up with something like this:

(s/def ::a string?)
(s/def ::b string?)
(s/def ::base-map (s/keys :req-un [::a ::b]))

(s/def
  :foo/bar
  (s/merge
   ::base-map
   (s/and
    (fn [m]
      (if (= (m :a) "bobo-included")
       (contains? m :bobo)
        true
        )))))
there’s probably better way of doing this Basically now :foo/bar is a spec for a map that must have :a and :b, keys, but when value of :a is equal to “bobo-included”, it must have :bobo, key, otherwise, :bobo key is optional

seancorfield 2020-01-29T03:37:14.106500Z

s/merge is intended for two key specs -- I don't think you should use it for a key spec and a predicate.

seancorfield 2020-01-29T03:37:52.107200Z

And it looks like you have s/and with just a single predicate there?

seancorfield 2020-01-29T03:38:29.107700Z

Now that you've restated the problem, it sounds like you want to look at multi-specs.

ag 2020-01-29T03:39:08.109Z

ah… yeah. let me try digging in there

seancorfield 2020-01-29T03:39:08.109100Z

Those are intended to select different specs based on some function of a value, in your case, the value of the :a key.

ag 2020-01-29T03:40:25.110400Z

well, yeah, I guess I didn’t exactly follow my own requirement, but the approach you showed me probably would work for me. I’m gonna refresh my memory on multi-specs anyway.

ag 2020-01-29T03:40:29.110600Z

Thank you Sean!

lambdam 2020-01-29T10:18:43.112200Z

Hello, I was playing a bit with spec 2 on an Advent of Code exercise and bumped into this case:

(def dam
  {:firstname "Dam"
   :age 35})

(s/def ::user (s/schema {:firstname string?
                         :lastname string?}))

(s/explain (s/select ::user [*])
           dam)
;; => {:firstname "Dam", :age 35} - failed: (fn [m] (contains? m :lastname))

(s/explain (s/select {:firstname string?
                      :lastname string?}
                     [*])
           dam)
;; => Success!

(s/explain (s/select [{:firstname string?
                       :lastname string?}]
                     [*])
           dam)
;; => {:firstname "Dam", :age 35} - failed: (fn [m] (contains? m :lastname))

(s/explain (s/select [{:firstname string?}
                      {:lastname string?}]
                     [*])
           dam)
;; => {:firstname "Dam", :age 35} - failed: (fn [m] (contains? m :lastname))
The second explain is weird (considering https://github.com/clojure/spec-alpha2/wiki/Schema-and-select#literal-schemas). Is there a subtle detail that I'm missing? (ping @alexmiller) Thanks

lambdam 2020-01-29T10:37:53.113800Z

Also, is there a specific reason not to instrument :ret and :fn in fdef (even optionally) in spec 2? I use https://github.com/jeaye/orchestra with spec 1 and find it very useful.

lambdam 2020-01-29T11:33:03.114900Z

Also

(s/valid?
  (s/schema {:foo string?})
  {:foo 1})
=> false

(s/assert
  (s/schema {:foo string?})
  {:foo 1})
=> {:foo 1}
... weird

alexmiller 2020-01-29T13:29:39.115100Z

https://clojure.org/guides/faq#instrument_ret

alexmiller 2020-01-29T13:38:57.115600Z

Dunno, probably a bug

lambdam 2020-01-29T14:45:16.116Z

Ah ok. Thanks.

zclj 2020-01-29T15:22:26.118200Z

I get an exception when using a schema referring a spec with an indirection to another spec. It works as expected if I just use the spec. Am I doing anything wrong here or is it a bug?

(s/def ::thing string?)
(s/def ::other-thing ::thing)
(gen/sample (s/gen ::other-thing)) ;; => works as expected

(s/def ::foo (s/schema [::thing]))
(s/def ::bar (s/schema [::other-thing]))
(gen/sample (s/gen ::foo)) ;; => works as expected
(gen/sample (s/gen ::bar)) ;; => Exception below
  
  No implementation of method: :conform* of protocol:
   #'clojure.alpha.spec.protocols/Spec found for class:
   clojure.lang.Keyword

alexmiller 2020-01-29T15:34:56.118600Z

this is a bug in spec 2, so won't work yet

kenny 2020-01-29T20:13:30.123Z

Does spec2 let you write a spec for a qualified keyword such that its definition changes depending on the context? For example, if I were to spec Datomic's :db/id, it would only ever be a nat-int? when part of the result is from a pull query. When transacting to Datomic, :db/id could be a lookup ref (e.g., (s/tuple keyword? some?) ), a nat-int? , or a string?. One could spec :db/id using s/or but that means everything that takes a db id as an input needs to handle all of those cases, which does not always make sense.

alexmiller 2020-01-29T20:52:15.123200Z

in short, no

alexmiller 2020-01-29T20:52:58.124Z

in long, the general recommendation is to try to give attributes specs that truly reflect the data

alexmiller 2020-01-29T20:53:31.124600Z

within certain contexts, you can add on additional specs that narrow the scope if needed

kenny 2020-01-29T21:00:18.125200Z

By this do you mean you can add additional predicates to the :db/id spec in different places?

alexmiller 2020-01-29T21:05:05.125700Z

yes, you could s/valid while s/and'ing in an additional narrower predicate for example

kenny 2020-01-29T21:05:41.125900Z

Oh cool. Is there an example of what that looks like?

alexmiller 2020-01-29T21:19:58.126800Z

just what I said?

kenny 2020-01-29T21:21:01.127Z

Oh - that works if the :db/id is taken as an argument itself. I was imagining a function that takes a map (perhaps nested) that has :db/ids on the maps.

kenny 2020-01-29T21:21:33.127200Z

Or do you mean needing to write a predicate that walks that structure validating db/id against this new predicate?

alexmiller 2020-01-29T21:39:01.127400Z

you can still s/and a predicate to a map that checks a value in the map

alexmiller 2020-01-29T21:39:13.127600Z

it's also an option to just not spec :db/id