通过 Clojure Spec 执行带有命名参数的宏
Exercising macros with named arguments through 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)
换句话说:似乎有一些隐藏的参数总是在正常调用期间传递给宏,而规范没有传递这些参数。遗憾的是,我对 Clojure 的经验不足,无法了解这些细节,但是一只小鸟告诉我,有一些东西名为 &env 和 &form。
但我的问题归结为:是否有可能以 spec/exercise-fn
可以很好地锻炼的方式指定带有命名参数的宏?
附录:
用 and
包装 keys*
似乎又破坏了 exercise-fn
,即使它有一个必需的命名参数。
您不能将 exercise-fn
与宏一起使用,因为您不能将 apply
与宏一起使用。 (注意这叫做练习 fn :).
这与 (apply dress ["foo"])
完全一样,它会产生熟悉的 "can't take value of a macro"。您看到的不同错误消息是因为它应用于 var 而不是宏,因为真正发生的事情就像 (apply #'user/dress ["foo"])
。
假设我们有一个宏,它接受一个必需的参数,后跟可选的位置参数,例如
(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)
换句话说:似乎有一些隐藏的参数总是在正常调用期间传递给宏,而规范没有传递这些参数。遗憾的是,我对 Clojure 的经验不足,无法了解这些细节,但是一只小鸟告诉我,有一些东西名为 &env 和 &form。
但我的问题归结为:是否有可能以 spec/exercise-fn
可以很好地锻炼的方式指定带有命名参数的宏?
附录:
用 and
包装 keys*
似乎又破坏了 exercise-fn
,即使它有一个必需的命名参数。
您不能将 exercise-fn
与宏一起使用,因为您不能将 apply
与宏一起使用。 (注意这叫做练习 fn :).
这与 (apply dress ["foo"])
完全一样,它会产生熟悉的 "can't take value of a macro"。您看到的不同错误消息是因为它应用于 var 而不是宏,因为真正发生的事情就像 (apply #'user/dress ["foo"])
。