clojure-spec

Cam Saul 2022-09-09T20:30:03.589769Z

What's the easiest way to do something like a "soft cut" e.g. core logic conda to lock a regex spec into validating an optional argument with a specific spec if it meets some condition, and failing otherwise? Suppose I have a spec like this:

(s/def ::my-map
  (s/keys :opt-un [...]))

(s/def ::my-spec
  (s/cat :my-map (s/? ::my-map) :options (s/* (s/cat :k keyword? :v any?))))
If I pass in a map that fails the ::my-map spec I get an error like
identity - failed: Extra input in: [0] spec: ::my-spec
because it then tries to use that argument as an option. How can I force it to validate maps with the ::my-map spec? I've tried stuff like (s/& map? ::my-map) but that doesn't work either

Alex Miller (Clojure team) 2022-09-09T21:28:58.702489Z

just checking - you have an optional followed by kv pairs, and with what input do you get this error? something that's neither a map or keyword as first arg?

Alex Miller (Clojure team) 2022-09-09T21:30:48.117339Z

I'm not sure if there's an easy alternative for you - seems like you could possibly treat this as two specs based on even/odd arg count. it's an unusual usage but you could probably build an s/multi that switched based on that and used different specs

Cam Saul 2022-09-09T21:33:01.329189Z

I ended up doing something like

(s/& 
 (s/cat :my-map (s/? map?) :options (s/* (s/cat :k keyword? :v any?)))
 (s/keys :opt-un [:my-map])
which works okay I guess but is a little icky I think

Cam Saul 2022-09-09T21:36:06.551409Z

Here's a more detailed example including something with neither a map nor keyword first arg. I just get an extra input error

(s/def ::k
  integer?)

(s/def ::my-map
  (s/keys :req-un [::k]))

(s/def ::my-spec
  (s/cat :my-map (s/? ::my-map) :options (s/* (s/cat :k keyword? :v any?))))

(s/explain-str ::my-spec [{:k 100}])
(s/explain-str ::my-spec [{:k 100} :x :y])
;; => "Success!\n"

(s/explain-str ::my-spec [100])
;; => "(100) - failed: Extra input in: [0] spec: ::my-spec\n"

(s/explain-str ::my-spec [{}])
;; => "({}) - failed: Extra input in: [0] spec: ::my-spec\n"

Cam Saul 2022-09-09T21:38:25.161879Z

defmulti is actually one real-world example of where you have a usage like this. The metadata attribute map is optional and after that it supports dispatch-fn as a positional argument and then a few optional key-value arguments like :default and :hierarchy if I recall correctly.

Cam Saul 2022-09-09T21:42:04.166689Z

This came up for me because I was working on a def macro that did something similar (supports and optional attribute map and keyword options) and I tried to tweak the spec to validate one of the keys in the attribute map specifically and it made things fail with confusing errors. In the example above I want the last failure to tell me it's failing because :k is not an integer?

Cam Saul 2022-09-09T21:46:10.186519Z

This is the working version I came up with:

(s/def ::my-spec
  (s/& (s/cat :my-map (s/? map?) :options (s/* (s/cat :k keyword? :v any?)))
       (s/keys :opt-un [::my-map])))

(s/explain-str ::my-spec [{}])
=> "{} - failed: (contains? % :k) in: [:my-map] at: [:my-map] spec: ::my-map\n"
I was wondering if maybe there was some easier way to do this, e.g. some way to define my so-called "soft cut" inline rather than having to wrap the whole thing in s/&

thiru 2022-09-09T21:30:19.232099Z

If I were to start using Spec today is it recommended to use Spec 1 or Spec 2? Also, my codebase is Clojure 1.8 and can't upgrade. The only issue I see with this is that ident? is missing but that's easy enough to add. Is there any strong reasons to avoid using Spec with Clojure versions before 1.9?

Alex Miller (Clojure team) 2022-09-09T21:31:08.905259Z

spec 2 has not been released and is not under active dev at this moment

🤔 1
Alex Miller (Clojure team) 2022-09-09T21:31:54.490089Z

I think someone did have a backport of spec at one point (we are not maintaining anything official though)

Alex Miller (Clojure team) 2022-09-09T21:32:22.239709Z

https://github.com/tonsky/clojure-future-spec

thiru 2022-09-09T21:33:23.541299Z

Excellent, thank you for the super fast response Alex! 🙂