通过Clojure规范使用命名参数练习宏

通过Clojure规范使用命名参数练习宏,clojure,clojure.spec,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")

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

(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
一件有趣的事情是,
练习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
可以给它一个很好的锻炼的方式来规范一个带有命名参数的宏

增编:


包装
键*
似乎会再次中断
练习fn
,即使它有一个必需的名称arg。

不能对宏使用
练习fn
,因为不能对宏使用
应用
。(请注意,它被称为练习fn:)


这与
(apply dress[“foo”])
完全相同,它产生了我们熟悉的“不能接受宏的值”。您看到的不同错误消息是因为它应用于var而不是宏,因为实际发生的情况类似于
(apply#'user/dress[“foo”])

啊。那么我们如何练习宏呢?
clojure.spec.test/check
是一种方法吗?请记住,宏(只是)一个接受代码并返回代码的函数。一般来说,用与函数相同的方式来执行宏是没有意义的,因为“调用”宏只会给您提供更多的编译内容。您可以使用:args生成器生成输入,然后使用这些输入评估调用<代码>检查过滤宏,因此也无法使用宏FDEF。