Reputation: 1926
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
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