Specifying an application function with clojure.spec

111 views Asked by At

Consider the following apply-like function:

(defn apply [f v]
  (f v))

I would like to specify this function in such a way that its return value satisfies the same specification as the return value of f. Furthermore I would also like to specify that the input value v satisfies the specification of the input of f.

My attempt at specifying something like this is the following:

(s/fdef apply
  :args (s/cat :f (s/fspec :args (s/cat :v any?) :ret any?))
  :ret any?)

This specification is very loose. What I would like to do is to quantify over two predicates returned? and argument? to further constrain the specification.

(s/fdef apply
  :args (s/cat :f (s/fspec :args (s/cat :v argument?) :ret returned?))
  :ret returned?)

Where argument? and returned? represent two arbitrary predicates. From what I understand this type of quantification is not possible in clojure.spec.

More generally, this type of behavior can be naturally expressed using any parametric type system, including Java's generics. Is this also specifiable using clojure.spec?

1

There are 1 answers

0
Rulle On

You would need some function to figure out the spec for the input argument f. I am not aware of any such function so here is one possibility. Yes, I know, it is pretty dirty:

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

(defn find-fn-spec [f]
  (first
   (for [[fvar fns] (-> #'stest/instrumented-vars deref deref)
         :when (or (= f (:wrapped fns))
                   (= f (:raw fns)))]
     (#'s/reg-resolve! (symbol fvar)))))

Then you can use that function to write your the spec of some other function that I call my-apply (there is already a function apply in core):

(defn my-apply [f v]
  (f v))

(s/fdef my-apply
  :args (s/and (s/cat :f fn? :x any?)
               (fn [input]
                 (let [{:keys [f x]} input]
                   (s/conform (:args (find-fn-spec f)) [x]))))
  :ret any?
  :fn (fn [{:keys [args ret]}]
        (s/valid? (:ret (find-fn-spec (first args))) ret)))

But I wouldn't recommend you doing this...