通过Clojure Spec

时间:2017-04-11 07:36:14

标签: clojure clojure.spec

假设我们有一个宏,它接受一个必需的参数,然后是可选的位置参数,如

(require '[clojure.spec     :as spec]
         '[clojure.spec.gen :as gen])

(defmacro dress [what & clothes]
  `(clojure.string/join " " '(~what ~@clothes)))

(dress "me")
=> "me"
(dress "me" :hat "favourite")
=> "me :hat favourite"

我们为它编写了一个规范,如

(spec/def ::hat string?)
(spec/fdef dress
           :args (spec/cat :what string?
                           :clothes (spec/keys* :opt-un [::hat]))
           :ret string?)

我们发现spec/exercise-fn无法行使宏

(spec/exercise-fn `dress)
;1. Unhandled clojure.lang.ArityException
;   Wrong number of args (1) passed to: project/dress

即使函数生成器生成的数据被宏接受也很好:

(def args (gen/generate (spec/gen (spec/cat :what string?
                                            :clothes (spec/keys* :opt-un [::hat])))))
; args => ("mO792pj0x")
(eval `(dress ~@args))
=> "mO792pj0x"
(dress "mO792pj0x")
=> "mO792pj0x"

另一方面,定义一个函数并以相同的方式运用它可以正常工作:

(defn dress [what & clothes]
  (clojure.string/join " " (conj clothes what)))

(spec/def ::hat string?)
(spec/fdef dress
           :args (spec/cat :what string?
                           :clothes (spec/keys* :opt-un [::hat]))
           :ret string?)
(dress "me")
=> "me"
(dress "me" :hat "favourite")
=> "me :hat favourite"
(spec/exercise-fn `dress)
=> ([("") ""] [("l" :hat "z") "l :hat z"] [("") ""] [("h") "h"] [("" :hat "") " :hat "] [("m") "m"] [("8ja" :hat "N5M754") "8ja :hat N5M754"] [("2vsH8" :hat "Z") "2vsH8 :hat Z"] [("" :hat "TL") " :hat TL"] [("q4gSi1") "q4gSi1"])

如果我们看一下具有相似定义模式的内置宏,我们会看到同样的问题:

(spec/exercise-fn `let)
; 1. Unhandled clojure.lang.ArityException
;    Wrong number of args (1) passed to: core/let

有一件有趣的事情是,当exercise-fn始终存在一个必需的命名参数时,(defmacro dress [what & clothes] `(clojure.string/join " " '(~what ~@clothes))) (spec/def ::hat string?) (spec/def ::tie string?) (spec/fdef dress :args (spec/cat :what string? :clothes (spec/keys* :opt-un [::hat] :req-un [::tie])) :ret string?) (dress "me" :tie "blue" :hat "favourite") => "me :tie blue :hat favourite" (spec/exercise-fn `dress) 可以正常工作:

spec/exercise-fn

换句话说:在正常调用期间似乎总是会有一些隐藏的参数传递给宏,这些参数不会被spec传递。可悲的是,我对Clojure的经验不足以了解这些细节,但是一只小鸟告诉我,有些东西名为& env和& form。

但是我的问题可以归结为:是否可以通过keys*为其提供良好锻炼的方式来指定具有命名参数的宏?

附录:

使用and包裹exercise-fn似乎再次中断{ "type": "kudu", "masterAddresses": "1.2.3.4", "enabled": true } ,即使它具有必需的命名arg。

1 个答案:

答案 0 :(得分:1)

您无法将exercise-fn与宏一起使用,因为您无法将apply与宏一起使用。 (注意它叫做练习 fn :)。

这与(apply dress ["foo"])完全相同,它产生熟悉的“无法获取宏的值”。您看到的不同错误消息是因为它应用于var而不是宏,因为实际发生的是(apply #'user/dress ["foo"])