【问题标题】:How do I spec higher order function arguments in Clojure?如何在 Clojure 中指定高阶函数参数?
【发布时间】:2017-07-12 21:52:45
【问题描述】:

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

(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/+ 显然不能使用。这意味着我可以通过将框的参数函数规范更改为来使其工作

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

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

注意为了让规范在我需要的 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 higher-order-functions clojure.spec


    【解决方案1】:

    我认为你不能用 clojure.spec 来表达这个函数的类型。你需要 type variables 才能写出类似的东西(这里使用 Haskell 风格的签名)

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

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

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-07-16
        • 1970-01-01
        • 2021-07-19
        • 2019-09-22
        • 1970-01-01
        • 2015-06-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多