为什么dotrace会在这里抛出StackOverflowError?

时间:2010-06-04 21:06:26

标签: clojure

(use '[clojure.contrib.trace])
(dotrace [str] (reduce str [\a \b]))

1 个答案:

答案 0 :(得分:9)

简而言之:

这是因为trace-fn-calldotrace用来包装要跟踪的函数,使用str来生成漂亮的TRACE foo => val输出。

扩展说明:

dotrace宏通过为每个包含要跟踪的函数的Var安装线程绑定来实现其魔力;在这种情况下,有一个这样的Var,clojure.core/str。替换看起来大致如此:

(let [f @#'str]
  (fn [& args]
    (trace-fn-call 'str f args)))

引用其文档字符串的trace-fn-call,“使用args跟踪对函数f的单个调用。”。在这样做时,它调用跟踪函数,记录返回值,打印出格式为TRACE foo => val的信息良好的消息,并返回从跟踪函数获得的值,以便可以继续定期执行。

如上所述,此TRACE foo => val消息是使用str生成的;但是,在手头的情况下,这实际上是被跟踪的函数,因此对它的调用导致另一个trace-fn-call的调用,这使得它自己尝试使用str生成跟踪输出字符串,这导致另一次调用trace-fn-call ......最终导致堆栈爆炸。

解决方法:

dotracetrace-fn-call的以下修改版本即使存在核心Vars的奇怪绑定也应该正常工作(请注意,期货可能不会及时安排;如果这是一个问题,请参阅下文) :

(defn my-trace-fn-call
  "Traces a single call to a function f with args.  'name' is the
  symbol name of the function."
  [name f args]
  (let [id (gensym "t")]
    @(future (tracer id (str (trace-indent) (pr-str (cons name args)))))
    (let [value (binding [*trace-depth* (inc *trace-depth*)]
                  (apply f args))]
      @(future (tracer id (str (trace-indent) "=> " (pr-str value))))
      value)))

(defmacro my-dotrace
  "Given a sequence of function identifiers, evaluate the body
   expressions in an environment in which the identifiers are bound to
   the traced functions.  Does not work on inlined functions,
   such as clojure.core/+"
  [fnames & exprs]
  `(binding [~@(interleave fnames
                           (for [fname fnames]
                             `(let [f# @(var ~fname)]
                                (fn [& args#]
                                  (my-trace-fn-call '~fname f# args#)))))]
     ~@exprs))

(在常规trace-fn-call周围重新绑定dotrace显然不起作用;我的猜测是因为clojure.* Var调用仍然被编译器硬连接,但这是一个单独的无论如何,上述方法都会奏效。)

另一种方法是使用上面的my-dotrace宏和my-trace-fn-call函数不使用期货,但修改为调用clojure.contrib.trace函数的自定义替换,使用以下代替str

(defn my-str [& args] (apply (.getRoot #'clojure.core/str) args))

替换是直截了当且乏味的,我在答案中省略了它们。