Clojure fn名称在提前编译时泄露到其范围之外

时间:2015-01-28 07:57:46

标签: recursion clojure stack-overflow

我想用fn生成命名函数并从宏返回它们,我尝试了以下示例:

(defmacro getfn
  [namestr children]
  `(fn fn-name# []
     (println "Recursing" ~namestr)
     (doall (map (fn [child#] (child#)) ~children))))

(def foo (getfn "foo" []))
(def bar (getfn "bar" [foo]))

(defn -main [& args]
  (bar))

结果输出通常与预期一致:

Recursing bar
Recursing foo

但是,当我运行此编译提前(AOT)时,我得到:

Recursing bar
Recursing bar
...
Recursing bar
Recursing bar
Exception in thread "main" java.lang.StackOverflowError

我觉得很奇怪bar一直在调用自己而不是foo,唯一合理的原因是生成的符号fn-name#泄漏到其范围之外。这是Clojure中的错误还是预期的行为?

更新:为清楚起见,请注意删除fn-name#符号并使函数匿名可修复此问题。但是,在我的实际代码中,我需要有时递归地调用它,因此命名它是必要的。

1 个答案:

答案 0 :(得分:1)

我对此问题的一个解决方案是使用gensym为每个版本的宏获取一个新符号,这可以通过修改getfn来实现,如下所示:

(defmacro getfn
  [namestr children]
  `(let [fn-name# (gensym)]
     (fn fn-name# []
       (println "Recursing" ~namestr)
       (doall (map (fn [child#] (child#)) ~children)))))

这感觉有点不必要,因为根据定义,fn名称应仅在其自己的范围内相关。

更新:刚刚测试了alpha版本,看起来Clojure 1.7.0-alpha3以及之后没有这个黑客的工作,Clojure 1.7.0-alpha2和更早版本都被打破了。除非有人能想到更好的东西,否则在发布1.7.0的稳定版本之前使用这种解决方法可能没问题。