如何在Clojure中规范更高阶函数参数?

时间:2017-07-12 21:52:45

标签: clojure higher-order-functions clojure.spec

让我们说我有一个函数接受一个函数并返回一个函数,该函数将任何参数应用于传入函数并将结果放入一个向量中(它是一个例子,但是希望能说明我的观点。)

(defn box [f]
  (fn [& args]
    [(apply f args)]))

我认为box函数的规范看起来像这样

(spec/fdef box
  :args (spec/cat :function (spec/fspec :args (spec/* any?)
                                        :ret any?))
  :ret (spec/fspec :args (spec/* any?)
                   :ret (spec/coll-of any? :kind vector? :count 1)))

如果我然后检测盒子功能

(spec-test/instrument)

和clojure.core / +的调用框我得到了一个异常

(box +)
ExceptionInfo Call to #'user/box did not conform to spec:
In: [0] val: ([]) fails at: [:args :function] predicate: (apply fn),  Cannot cast clojure.lang.PersistentVector to java.lang.Number
:clojure.spec.alpha/args  (#function[clojure.core/+])
:clojure.spec.alpha/failure  :instrument
:clojure.spec.test.alpha/caller  {:file "form-init4108179545917399145.clj", :line 1, :var-scope user/eval28136}
  clojure.core/ex-info (core.clj:4725)

如果我正确理解了错误,那么它会接受任何错误吗?谓词并为测试生成PersistentVector,clojure.core / +显然无法使用。这意味着我可以通过将box的参数函数规范更改为

来使其工作
(spec/fspec :args (spec/* number?)
            :ret number?)

但是如果我想为clojure.core / +和clojure.string / lower-case使用box呢?

N.B。要使规范在我需要的REPL中工作

:dependencies [[org.clojure/clojure "1.9.0-alpha16"]]
:profiles {:dev {:dependencies [[org.clojure/test.check "0.9.0"]]}}
:monkeypatch-clojure-test false
project.clj中的

和以下导入

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

2 个答案:

答案 0 :(得分:3)

我不认为你可以用clojure.spec表达这个功能的类型。您需要type variables才能编写类似的内容(此处使用Haskell样式的签名)

box :: (a -> b) -> (a -> [b])

也就是说,能够捕获"这一点非常重要。输入函数f的规范,并在输出规范中包含它的一部分。但据我所知,在clojure.spec中没有这样的东西。您还可以看到clojure.spec' s list of specs for built-in functions没有为例如clojure.core/map定义规范,这会产生同样的问题。

答案 1 :(得分:0)

@amalloy's answer类似,高阶函数返回值的类型(spec)取决于您给出的参数。如果你提供一个可以对数字进行操作的函数,那么HOF返回的函数也可以对数字进行操作;如果它适用于字符串,然后是字符串,依此类推。所以,你需要以某种方式继承/反思参数函数的(规范),为HOF提供正确的输出规范,我无法思考如何。

在任何情况下,我都会选择为不同的用例创建单独的函数(别名):

(def any-box box)

(def number-box box)

然后,您可以单独指定这些:

(spec/fdef any-box ;... like your original spec for box

(spec/fdef number-box
  :args (spec/cat :function (spec/fspec :args (spec/* number?)
                                        :ret number?))
  :ret (spec/fspec :args (spec/* number?)
                   :ret (spec/coll-of number? :kind vector? :count 1)))

规格与预期的工具配合使用:

(spec-test/instrument)

(number-box +)
(any-box list)

当然,如果你有很多这样的用例,为每个用例编写一个规范可能是相当费力的。