Clojure の式の評価を追跡するツール eyewrap

user> (cap (* 2 (+ (- 3 8) 4)))
0 : + (* 2 (+ (- 3 8) 4))
1 : +   (+ (- 3 8) 4)
2 : +     (- 3 8)
2 :=>     -5
1 :->   (+ -5 4)
1 :=>   -1
0 :-> (* 2 -1)
0 :=> -2
-2

fatrow's eyewrap at master - GitHub

式の返してくる値だけじゃなくて、中でどの式がどう評価されているかを観察したいという目的のために作りました。仕組みは単純で、値を返す前に保存してから同じ値を返し、あたかも元の式のままであるようなコードを生成するマクロになっています。値を返す前に保存してから同じ値を返すという仕組み上、残念ながら末尾再帰の recur は使えません。引数として渡されたコードは完全にマクロ展開されてトレースに表示されます。ですので元のコードとの対応関係が分かりにくいかもしれません。

  • 使い方

Leiningen を使用します。 project.clj に [eyewrap "0.x.x"] を追加。現在のバージョンはClojarsを参照して下さい。

:dev-dependencies [[eyewrap "0.x.x"]]
% lein deps

use しておく。

user> (use 'hozumi.eyewrap)
nil
  • 普通の式をトレースする

cap マクロにそのまま式を渡してやります。

(cap <code>)

簡単な計算

user> (cap (+ 1 (* 2 3)))
0 : + (+ 1 (* 2 3))
1 : +   (* 2 3)
1 :=>   6
0 :-> (+ 1 6)
0 :=> 7
7

左の数字はその行の表示している階層レベルです。階層レベルによってインデントしています。 + は今から評価しようとしている式。 => は評価結果。 -> は子供の式を評価した結果、親の式はどうなったかを表しています。

  • 関数をトレースする
(cap 適当な名前 (defn ...))

以下の引数で与えた関数 touch はどういう動作をするでしょうか。

user> (cap ppp (defn touch [coll target-index]
		 (-> [(coll target-index)]
		     (into (subvec coll 0 target-index))
		     (into (subvec coll (inc target-index))))))
#'user/touch

まず、defn とは別に第一引数で与えられた名前で関数が作成され、その関数を使用してトレースを表示します。

user> ppp
#<user$eval__6979$ppp__6981 user$eval__6979$ppp__6981@680e62df>

まだ、何も入っていません。

user> (ppp)
:const

defn で作成した関数を実行すると、実行時の各値が保存されます。

user> (touch [:a :b :c :d] 3)
[:d :a :b :c]

トレースを表示。

user> (ppp)
0 : + (into (into [(coll target-index)] (subvec coll 0 target-index)) (subvec coll (inc target-index)))
1 : +   (into [(coll target-index)] (subvec coll 0 target-index))
2 : +     [(coll target-index)]
3 : +       (coll target-index)
3 :->       ([:a :b :c :d] target-index)
3 :->       ([:a :b :c :d] 3)
3 :=>       :d
2 :->     [:d]
2 :=>     [:d]
1 :->   (into [:d] (subvec coll 0 target-index))
2 : +     (subvec coll 0 target-index)
2 :->     (subvec [:a :b :c :d] 0 target-index)
2 :->     (subvec [:a :b :c :d] 0 3)
2 :=>     [:a :b :c]
1 :->   (into [:d] [:a :b :c])
1 :=>   [:d :a :b :c]
0 :-> (into [:d :a :b :c] (subvec coll (inc target-index)))
1 : +   (subvec coll (inc target-index))
1 :->   (subvec [:a :b :c :d] (inc target-index))
2 : +     (inc target-index)
2 :->     (inc 3)
2 :=>     4
1 :->   (subvec [:a :b :c :d] 4)
1 :=>   []
0 :-> (into [:d :a :b :c] [])
0 :=> [:d :a :b :c]
[:d :a :b :c]

recur は使えないし、バグも沢山あると思うのであまり実用的じゃないですが、簡単な処理をちょっと見てみたいという時にはいいかもしれません。