Clojure 1.2.0 で再帰的呼び出しを dotrace で補足できない件
dotrace マクロを使うと、指定した関数の呼び出しの引数、返り値をトレースできる。
user> (use 'clojure.contrib.trace) nil user> (defn f [n] (* n n)) #'user/f user> (dotrace [f] (doseq [i [1 2 3]] (f i))) TRACE t7412: (f 1) TRACE t7412: => 1 TRACE t7413: (f 2) TRACE t7413: => 4 TRACE t7414: (f 3) TRACE t7414: => 9 nil
のだが、 Clojure 1.2.0 では、再帰的呼び出しに対して dotrace がうまく動かなくなってしまった。
user> (defn fact [n] (if (< n 2) n (* n (fact (dec n))))) #'user/fact user> (dotrace [fact] (fact 5)) TRACE t7499: (fact 5) TRACE t7499: => 120 120
Clojure 1.1.0 だと、以下のようにきれいに表示される。
user> (use 'clojure.contrib.trace) nil user> (defn fact [n] (if (< n 2) n (* n (fact (dec n))))) #'user/fact user> (dotrace [fact] (fact 5)) TRACE t14: (fact 5) TRACE t15: | (fact 4) TRACE t16: | | (fact 3) TRACE t17: | | | (fact 2) TRACE t18: | | | | (fact 1) TRACE t18: | | | | => 1 TRACE t17: | | | => 2 TRACE t16: | | => 6 TRACE t15: | => 24 TRACE t14: => 120 120
で、何故なんだろうと調べてみると、 Can clojure be made fully dynamic? - Stack Overflow に突き当たった。
どうやら、関数内では、その関数自身の名前を Var として名前解決するのではなく、lexical binding として持つようになったらしい。従って、 dotrace が動的に Var を既存の関数をラップした新しい関数で再束縛しても、再帰的呼び出し部分は、常に名前解決の優先順位の高い lexical binding ( lexical binding は再束縛できない ) を見ていて、変更された Var のほうを見ることができない、ということらしい。
そのため、 dotrace で再帰的な呼び出しを補足したい場合は、定義内の再帰的呼び出しを Var で名前解決するように #'var と指定してやる必要がある。
user> (defn fact2 [n] (if (< n 2) n (* n (#'fact2 (dec n))))) #'user/fact2 user> (dotrace [fact2] (fact2 5)) TRACE t14091: (fact2 5) TRACE t14092: | (fact2 4) TRACE t14093: | | (fact2 3) TRACE t14094: | | | (fact2 2) TRACE t14095: | | | | (fact2 1) TRACE t14095: | | | | => 1 TRACE t14094: | | | => 2 TRACE t14093: | | => 6 TRACE t14092: | => 24 TRACE t14091: => 120 120