我如何在 Clojure 中指定高阶函数参数?
How do I spec higher order function arguments in Clojure?
假设我有一个函数,它接受一个函数和 returns 一个函数,该函数将它提供给传入函数的任何参数应用并将结果放入向量中(这是一个很好的例子,但会希望能说明我的观点)。
(defn box [f]
(fn [& args]
[(apply f args)]))
我认为盒子函数的规范是这样的
(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 使用 box 怎么办。string/lower-case?
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])
我认为你不能用 clojure.spec 来表达这个函数的类型。你需要 type variables 才能写出类似的东西(这里使用 Haskell 风格的签名)
box :: (a -> b) -> (a -> [b])
也就是说,重要的是您能够 "capture" 输入函数 f 的规范并将其部分包含在输出规范中。但是据我所知clojure.spec里面没有这样的东西。您还可以看到 clojure.spec 的 list of specs for built-in functions 没有定义规范,例如 clojure.core/map
,这也会有同样的问题。
如 所说,高阶函数的 return 值的类型(规格)取决于您给它的参数。如果你提供了一个可以对数字进行操作的函数,那么HOFreturns这个函数也可以对数字进行操作;如果它适用于字符串,则字符串,依此类推。因此,您需要以某种方式 inherit/reflect 在参数函数的(规范)上为 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)
当然,如果您有很多用例,则为每个用例编写规范可能会很费力。
假设我有一个函数,它接受一个函数和 returns 一个函数,该函数将它提供给传入函数的任何参数应用并将结果放入向量中(这是一个很好的例子,但会希望能说明我的观点)。
(defn box [f]
(fn [& args]
[(apply f args)]))
我认为盒子函数的规范是这样的
(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 使用 box 怎么办。string/lower-case?
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])
我认为你不能用 clojure.spec 来表达这个函数的类型。你需要 type variables 才能写出类似的东西(这里使用 Haskell 风格的签名)
box :: (a -> b) -> (a -> [b])
也就是说,重要的是您能够 "capture" 输入函数 f 的规范并将其部分包含在输出规范中。但是据我所知clojure.spec里面没有这样的东西。您还可以看到 clojure.spec 的 list of specs for built-in functions 没有定义规范,例如 clojure.core/map
,这也会有同样的问题。
如
无论如何,我会选择为不同的用例创建单独的函数(别名):
(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)
当然,如果您有很多用例,则为每个用例编写规范可能会很费力。