这是我失败的尝试:
(defmacro until
[condition body setup increment]
`(let [c ~@condition]
(loop [i setup]
(when (not c)
(do
~@body
(recur ~@increment))))))
(def i 1)
(until (> i 5)
(println "Number " i)
0
(inc i))
Run Code Online (Sandbox Code Playgroud)
我得到:CompilerException java.lang.RuntimeException:不能让限定名:clojure-noob.core/c
我期待这个输出:
Number 1
Number 2
Number 3
Number 4
Number 5
怎么了?
宏存在一些问题:
#.否则,宏中的绑定可能会掩盖代码中其他位置的绑定.~@而不是~这是将编译/扩展的宏版本:
(defmacro until [condition body setup increment]
`(let [c# ~condition]
(loop [i# ~setup]
(when-not c#
~body
(recur ~increment)))))
Run Code Online (Sandbox Code Playgroud)
但是这将在你的例子中永远循环,因为condition它只被评估一次,而且i价值永远不会改变.我们可以解决这个问题
(defmacro until [condition body increment]
`(loop []
(when-not ~condition
~body
~increment
(recur))))
Run Code Online (Sandbox Code Playgroud)
i如果我们想要改变它的值,我们需要变成可变的:
(def i (atom 1))
(until (> @i 5)
(println "Number " @i)
(swap! i inc))
;; Number 1
;; Number 2
;; Number 3
;; Number 4
;; Number 5
Run Code Online (Sandbox Code Playgroud)
但现在until开始看起来很像补充while,其额外的复杂性似乎并不有益.
(defmacro until [test & body]
`(loop []
(when-not ~test
~@body
(recur))))
Run Code Online (Sandbox Code Playgroud)
除了测试被反转之外,这个版本until是相同的while,并且带有原子的上面的示例代码仍然表现正确.我们可以until通过while直接使用进一步简化,它最终会扩展到相同的代码:
(defmacro until [test & body]
`(while (not ~test) ~@body))
Run Code Online (Sandbox Code Playgroud)