首页 > 解决方案 > 实现直到作为宏

问题描述

这是我失败的尝试:

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

我得到:CompilerException java.lang.RuntimeException:不能让限定名称:clojure-noob.core/c

我期待这个输出: Number 1 Number 2 Number 3 Number 4 Number 5

怎么了?

标签: clojuremacros

解决方案


宏有几个问题:

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

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

(defmacro until [condition body setup increment]
  `(let [c# ~condition]
     (loop [i# ~setup]
       (when-not c#
         ~body
         (recur ~increment)))))

但这将在您的示例中永远循环,因为condition只评估一次并且i' 的值无论如何都不会改变。我们可以解决这个问题:

(defmacro until [condition body increment]
  `(loop []
     (when-not ~condition
       ~body
       ~increment
       (recur))))

i如果我们想改变它的值,我们需要使其可变:

(def i (atom 1))

(until (> @i 5)
  (println "Number " @i)
  (swap! i inc))
;; Number  1
;; Number  2
;; Number  3
;; Number  4
;; Number  5

但是现在until开始看起来很像 的补集while,而且它的额外复杂性似乎没有什么好处。

(defmacro until [test & body]
  `(loop []
     (when-not ~test
       ~@body
       (recur))))

这个版本until是相同的,while除了测试被反转,上面带有原子的示例代码仍然表现正确。我们可以until通过while直接使用进一步简化,最终会扩展为相同的代码:

(defmacro until [test & body]
  `(while (not ~test) ~@body))

推荐阅读