3

Let's say I have a function that takes a function and returns a function that applies any arguments it is given to the passed in function and puts the result in a vector (it's a noddy example, but will hopefully illustrate my point).

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

I think the spec for the box function looks like this

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

If I then instrument the box function

(spec-test/instrument)

and call box with clojure.core/+ I get an exception

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

If I understand the error correctly then it's taking the any? predicate and generating a PersistentVector for the test, which clojure.core/+ obviously can't use. This means I can get it to work by changing box's argument function spec to be

(spec/fspec :args (spec/* number?)
            :ret number?)

but what if I want to use box for both clojure.core/+ and clojure.string/lower-case?

N.B. To get spec to work in the REPL I need

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

in project.clj and the following imports

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

2 回答 2

3

我不认为你可以用 clojure.spec 来表达这个函数的类型。您需要类型变量才能编写类似的东西(这里使用 Haskell 样式的签名)

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

也就是说,重要的是您能够“捕获”输入函数 f 的规范并将其部分包含在输出规范中。但据我所知,clojure.spec 中没有这样的东西。您还可以看到 clojure.spec 的内置函数规范列表没有定义例如 的规范clojure.core/map,这将有同样的问题。

于 2017-07-12T22:36:21.597 回答
0

就像@amalloy 的回答所说,您的高阶函数返回值的类型(规范)取决于您给它的参数。如果你提供了一个可以对数字进行操作的函数,那么 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)

当然,如果你有很多用例,为每个用例编写规范可能是一项艰巨的工作。

于 2017-07-13T10:34:01.180 回答