实现直到作为宏

时间:2018-05-13 23:37:35

标签: clojure macros

这是我失败的尝试:

(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

怎么了?

2 个答案:

答案 0 :(得分:6)

宏存在一些问题:

  • 您需要为宏内部的绑定生成符号。一种方便的方法是使用for (let j = 0; i < item.list.length; j++) { if (item.hasSubList) { let list = item.list[j] as Menu; // use 'as' for (let k = 0; k < list.list.length; k++) { console.log(list[k]); } } else { let list = item.list[j]; console.log(list); } } 作为名称的后缀。否则,宏中的绑定可能会掩盖代码中其他位置的绑定。
  • 有些宏输入在不加引号时不必要地拼接,即#而不是~@

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

~

但是这会在你的例子中永远循环,因为(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

答案 1 :(得分:1)

也改变let行:

... 
`(let [c# ~@condition]
... 

然后将c的所有引用重命名为c#。后缀#生成唯一的,非命名空间限定的标识符,以确保宏创建的符号不会与宏扩展到的上下文中的任何现有符号冲突。每当您使用引号形式绑定符号时,您应该使用#来防止冲突,除非您有充分的理由不使用它。

为什么在这种情况下这是必要的?我不记得确切的原因,但是如果我没记错的话,在语法引用形式(`())中绑定的任何符号都是名称空间限定的,并且您不能使用let创建名称空间限定符号。

您可以输入以下内容重新创建错误:

(let [a/a 1]
  a/a)