使用clojure.spec指定类apply函数的规格
在Clojure.spec中实现泛型风格的函数约束
问题背景
现有这个apply函数:
(defn apply [f v] (f v))
我们需要给它定义规格,要求:
- 入参
v必须符合函数f的入参规格 apply的返回值必须和f的返回值规格一致
之前写的规格过于宽松,完全没体现这种关联约束:
(s/fdef apply :args (s/cat :f (s/fspec :args (s/cat :v any?) :ret any?)) :ret any?)
本来想用量化谓词argument?和returned?来实现关联约束,类似这样的伪代码:
(s/fdef apply :args (s/cat :f (s/fspec :args (s/cat :v argument?) :ret returned?)) :ret returned?)
但这种参数化写法在Clojure.spec里无法直接使用,想知道有没有可行的实现方式。
实现方案
可以实现,根据你使用的Clojure.spec版本,有两种对应方式:
方式一:适用于Clojure.spec 1(clojure.spec.alpha)
通过自定义验证逻辑,从f的规格中提取对应规则来验证v和返回值:
(require '[clojure.spec.alpha :as s]) (s/fdef apply :args (s/cat :f (s/fspec :args (s/cat :v any?) :ret any?) :v (s/arg (fn [args v] (let [f-spec (s/get-spec (:f args)) v-spec (-> f-spec :args :v)] (s/valid? v-spec v))) "v必须匹配f的入参规格")) :ret (s/ret (fn [args ret-val] (let [f-spec (s/get-spec (:f args)) ret-spec (:ret f-spec)] (s/valid? ret-spec ret-val))) "返回值必须匹配f的返回值规格"))
方式二:适用于Clojure.spec 2(clojure.spec.alpha2)
spec 2支持直接关联同一fdef内的规格,写法更简洁,类似泛型的参数化效果:
(require '[clojure.spec.alpha2 :as s]) (s/fdef apply :args (s/cat :f (s/fspec :args (s/cat :v ::arg) :ret ::ret) :v ::arg) :ret ::ret)
说明
- 第一种方式里,
s/arg和s/ret允许我们基于整个入参集合(args)做自定义验证,动态提取f的规格规则来检查v和返回值。 - 第二种方式利用spec 2的新特性,用命名关键字
::arg和::ret自动绑定f的fspec里对应的规格,实现了和Java泛型类似的参数化约束。
内容的提问来源于stack exchange,提问作者MPaga
相关产品推荐
相关产品推荐

