实现直到作为宏

Ada*_*ver 1 macros clojure

这是我失败的尝试:

(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

怎么了?

Tay*_*ood 7

宏存在一些问题:

  • 您需要为宏内部的绑定生成符号.一个方便的方法是使用名称后缀#.否则,宏中的绑定可能会掩盖代码中其他位置的绑定.
  • 一些宏输入在不加引号时被不必要地拼接,~@而不是~

这是将编译/扩展的宏版本:

(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)