使用 clojure 规范验证在运行时传入我的函数的匿名函数

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

假设我有一个函数 a,它接受一个函数 (fn) 作为参数:

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

为了给调用者一个很好的错误消息,我会在运行时验证 fn 参数。

这可能吗,我该怎么做?我可以只在运行时 fdef 这个,然后在调用时检测它吗,比如:

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

从哲学的角度来看,这是明智的还是有意义的?

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

不,因为 instrument 接受一个符号,解析它的 var,并实质上用一个包装原始函数的新函数替换它——这就是验证输入参数的工作完成的地方。在您的示例中,您将无权访问 f 函数的 "original" 符号,并且匿名函数不会解析为 var.

您可以指定高阶函数,因此您可以这样指定 a

(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

但是... 请注意,作为 f 传递给检测 a 的任何函数都将使用随机输入调用多次!这就是 spec 确定函数是否符合的方式。

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

显然你不想将它与副作用或昂贵的功能一起使用。我只建议对 testing/development 使用 instrument。您也可以在函数中使用 s/assert,但这仍会多次调用 f

(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?

这实际上取决于您的程序的性质以及作为 f 传递的函数的可能域。