Marten Sytema
Marten Sytema

Reputation: 1926

Validating anonymous functions passed in to my function at runtime, with clojure spec

Say I have a function a that takes a function (fn) as an argument:

(defn a [f] ....).

For the sake of giving a nice error message to the caller, I would at runtime validate the fn argument.

Is this possible, and how would I go about this? Can I just at runtime fdef this, and then instrument it upon calling, something like:

(defn a [f] 
   ;;.... spec f here on the fly, and call f, throw exception if f 
   ;; does not conform to spec
 )

Is that wise or does it make sense from a philosophical stand point?

Upvotes: 1

Views: 357

Answers (1)

Taylor Wood
Taylor Wood

Reputation: 16194

Can I just at runtime fdef this, and then instrument it upon calling [...]

No, because instrument takes a symbol, resolves its var, and essentially replaces it with a new function that wraps the original — that's where the work of validating the input arguments is done. In your example you won't have access to the f function's "original" symbol, and anonymous functions don't resolve to a var.

You can spec higher-order functions, so you could spec a like this:

(defn a [f] (f (rand-int)))
(s/fdef a :args (s/cat :f (s/fspec :args (s/cat :x number?))))
(st/instrument `a)
(a inc) ;; => 2
(a str) ;; => "1"
(a (fn [x] (assoc x :foo 'bar))) ;; spec error b/c fn doesn't conform

But... beware that any function passed as f to instrumented a will be invoked several times with random inputs! This is how spec determines if the function conforms.

(a #(doto % prn inc))
-1
-0.75
...
=> 0.18977464236944408

Obviously you wouldn't want to use this with side-effecting or expensive functions. I would only recommend using instrument for testing/development. You could also use s/assert in your function, but this will still invoke f multiple times.

(s/def ::f (s/fspec :args (s/cat :x number?)))
(defn a [f]
  (s/assert ::f f)
  (f (rand)))
(s/check-asserts true)

Is that wise or does it make sense from a philosophical stand point?

It really depends on the nature of your program and the possible domain of functions passed as f.

Upvotes: 3

Related Questions