Clojure spec.alpha - How to (reference another argument) / (describe that argument collection should include values from another argument collection)

173 views Asked by At

What I need: a spec for a function, that has two arguments:

  • a hash-map of keywords and strings.
  • a vector that may have strings or keywords but if it is a keyword it must exist inside hash-map (first argument)

(your answer doesn't have to cover all of this, mainly I need a way to tell that if it is a keyword it must exist in hash-map)

Here is what I have:

(this is an example to show that it is possible to access both arguments inside :args, I know that it doesn't test anything and always fails because nil is returned)

(ns my-example.core
  (:require
   [clojure.spec.alpha :as spec]))

(defn my-example [m v] nil)

(spec/fdef my-example
  :args (fn [[m v]] nil))

This fn kind of works (it is possible to create a function that would work how I want), But it isn't very descriptive and when it fails (given that there is (stest/instrument `my-example)) it just shows me body of function (like this: (fn [[m v]] nil)).

Is this the only way to solve my problem or there is a better way?

I also tryed to define a spec and use it inside :args :

(spec/def :my-example/my-check (fn [[m v]] nil))

(spec/fdef my-example
  :args :my-example/my-check)

But result is same.

1

There are 1 answers

4
dorab On BEST ANSWER

In the spec for :args, you can specify any predicate you want. See the example provided at the spec guide for fdef. Given that example, here is a code fragment that mostly works for your case. I say "mostly" because the spec for the first map argument could be made stricter to note that it is a map of keywords to strings. The forms inside the comment form show some usage examples.

(ns example
  (:require [clojure.spec.alpha :as s]
            [clojure.spec.test.alpha :as stest]))

(defn my-example [m v] nil)

(s/fdef my-example
  :args (s/and (s/cat :m map? :v vector?)
               #(every? (fn [x] (or (string? x)
                                    (and (keyword? x)
                                         (contains? (:m %) x))))
                        (:v %)))
  :ret nil?)

(comment
  (stest/instrument `my-example)
  (my-example {:a "a" :b "b"} ["foo" :a "bar" :b]) ; => nil
  (my-example {:a "a" :b "b"} ["foo" :a "bar" :c]) ; => spec exception
  (my-example {:a "a" :b "b"} ["foo" :a "bar" 2]) ; => spec exception
  )