`loop`和`with-redefs`不能很好地配合

时间:2018-11-12 12:55:58

标签: clojure

我有一些代码经过重构,只是发现某些内容被loop破坏了。经过一些调试后,我发现loopwith-redefs不能很好地配合使用。我意识到在循环中使用with-redefs可能没有意义,但我没想到它不会起作用。我不确定它是否是故意的。

这是我为演示“问题”而创建的MCVE:

(loop [test 3]
  (with-redefs []
    (if (zero? test)
      "done"
      (recur (dec test)))))

这给了我

  

重复出现的参数计数不匹配,预期:0 args,得到:1

删除with-redefs的操作符合预期:

(loop [test 3]
  (if (zero? test)
    "done"
    (recur (dec test))))

并返回"done"

第一段代码不起作用的原因是什么?这是故意的吗?

1 个答案:

答案 0 :(得分:8)

解释在with-redefs的宏扩展中:

(macroexpand-1
 '(with-redefs []
    (if (zero? test)
      "done"
      (recur (dec test)))))

返回:

(with-redefs-fn {}
  (fn []
    (if (zero? test)
      "done"
      (recur (dec test)))))    

在其中您可以看到,因为引入了新的fn,所以recur将引用该fn而不是更远的loop(解释了Arity异常)。

还有许多其他与loop“不兼容”的宏,因为recur必须相对于loop处于尾部位置,如果recur发生在宏调用内,则该宏可能正在操纵代码,使得recur不再位于尾部位置。

特别是对于with-redefs(以及其他各种情况),一种解决方法可能是:

(loop [test 3]
  (let [[recur? val]
        (with-redefs []
          (if (zero? test)
            [false "done"]
            [true (dec test)]))]
    (if recur?
      (recur val)
      val)))