如何在Clojure中为高阶函数参数规定规格?

3
假设我有一个函数,它接收另一个函数并返回一个函数,该函数将其获得的任何参数应用于传入的函数,并将结果放入一个向量中(这只是一个简单的示例,但希望能说明我的观点)。
(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)

如果我正确理解了错误,那么它正在使用any?谓词并为测试生成一个PersistentVector,而clojure.core/+显然无法使用它。这意味着我可以通过将box的参数函数规范更改为以下内容来使其正常工作:

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

但是如果我想同时使用clojure.core/+和clojure.string/lower-case,怎么办?

注意:为了让spec在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个回答

3

我认为你不能使用clojure.spec来表达这个函数的类型。你需要使用类型变量才能够像下面这样编写内容(这里使用了Haskell风格的签名)。

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

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

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)

当然,为每个用例编写规范可能是一项相当费力的工作,特别是如果你有很多用例。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接