【问题标题】:Exercising macros with named arguments through Clojure Spec通过 Clojure Spec 使用命名参数执行宏
【发布时间】:2017-04-11 07:36:13
【问题描述】:

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

(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,即使它有一个必需的命名arg。

【问题讨论】:

    标签: clojure clojure.spec


    【解决方案1】:

    您不能将exercise-fn 与宏一起使用,因为您不能将apply 与宏一起使用。 (请注意,它被称为练习 fn :)。

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

    【讨论】:

    • 啊。那么我们如何使用我们的宏呢? clojure.spec.test/check 是要走的路吗?
    • 请记住,宏是(只是)一个接受代码并返回代码的函数。以与执行函数相同的方式执行宏通常没有意义,因为“调用”它只会为您提供更多要编译的东西。您可以使用 :args 生成器生成输入,然后使用我想的这些输入来评估调用。 check 过滤宏,因此也不适用于宏 fdef。
    猜你喜欢
    • 1970-01-01
    • 2011-03-21
    • 1970-01-01
    • 1970-01-01
    • 2013-09-27
    • 2012-10-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多