Fork me on GitHub
#clojure-spec
<
2019-05-14
>
rickmoynihan12:05:55

Any ideas on how to spec this? Essentially I have a heterogenous map; if a key in that map is a vector of length N then I’d like to spec that the value of that key in the map is a sequence of tuples also of length N. If a key in the map is not a vector, than its key can be any?

alexmiller13:05:50

spec it as a collection of kv tuples

👍 1
Alex Whitt20:05:58

When defining your fspec's :fn, what's the intended way to interact with args that are s/or specs? For example: {:fn #(-> % :args :arg-name) for such an arg yields a vector that starts with the keyword for the s/or variant. Here's how I could approach it:

(s/def ::example (s/or :map map?
                       :num int?))

(defn myfn [int-arg example-arg])

(s/fdef myfn
  :args (s/cat :int-arg int?
               :example-arg ::example)
  :fn #(-> % :args :example-arg
           (as-> [path value]
               (or (not= path :map)
                   (contains? value (-> % :args :int-arg))))))

(myfn 1 {2 :a 3 :b}) ;; => fails
Here, :fn ensures that the first arg is a key in the second arg if the second arg is a map. Is this how I should be writing these :fns, or is there a better way?

alexmiller21:05:22

if it's just an inter-arg dependency, you can s/& that predicate onto the args spec

alexmiller21:05:53

rather than using :fn, which has access to both args and ret

alexmiller21:05:32

alternately, you could encode the args with an s/alt for your two alternatives

alexmiller21:05:09

(s/alt :not-map (s/cat :int-arg int? :example-arg #(not (map? %)))
       :map (s/& (s/cat :int-arg int? :example-arg map?) #(contains? %2 %1)))

alexmiller21:05:25

something like that for the args if I understood all that right

alexmiller21:05:16

and as an aside, when something is hard to spec like this with options, it's often a good sign that your function is doing two things and having 2 functions might be better