tools.traceで実行トレースをとる

(このエントリは Clojure Contrib Library Advent Calendar 7日目の記事です。)

概要

今回はtools.traceについてです。tools.traceは実行トレースをとるのに便利な関数/マクロを提供するライブラリです。標準でデバッグ機能が弱いClojure上での開発で,シンプルに関数呼び出しだけトレースしたいような場合に使えます。

インストール

Leiningenを使用している場合は,project.cljの:dependenciesに

[org.clojure/tools.trace "0.7.6"]

と追加するだけで使えます。2013年12月現在では,0.7.6が最新の安定版です。
基本的には,tools.traceは開発時に使用するライブラリなので,:devプロファイルの:dependenciesとして追加するのがいいでしょう。

使い方

tools.traceが提供する主な関数/マクロは以下です。

(trace-vars & vs)
トレースする関数の名前を指定すると,その関数のVarが束縛する値をトレース出力つきの関数に置き換えます。
(untrace-vars & vs)
trace-varsで置き換えた関数を元に戻すことができます。
(trace value)
関数呼び出し以外にトレース結果として出力したい値がある場合に使用します。

以下のマクロについては,使用する場合に注意するか,使用しない方がよいでしょう。

dotrace
マクロのボディ内だけ関数をトレース出力つきの関数に動的束縛する関数です。^:dynamicとして定義されたVar以外には適用できません。これは,1.3以降でVarがデフォルトで:dynamicでなくなったことに依ります。
trace-forms
引数に与えた式のあらゆるサブフォームに対して,トレース出力を行う式を挿入します。コードウォークの実装が雑なため,引数に与える式によっては式の挿入に失敗します。
trace-ns
引数に与えた名前空間で定義されたすべての関数に対してtrace-varsを適用し,トレース出力つきの関数に置き換えます。マップやセットなどclojure.lang.IFnインタフェースを実装した値を関数と誤判定し,トレース出力つきの関数に置き換えるため,トップレベルにマップやセットの定義がある場合に問題になることがあります。

適用例

以下のようなプログラムを対象にトレース出力の結果を見てみましょう。

(ns even-and-odd
  (:refer-clojure :exclude [even? odd?]))

(declare odd?)

(defn even? [x]
  (or (= x 0)
      (odd? (dec x))))

(defn odd? [x]
  (and (not= x 0)
       (even? (dec x))))

以下がtools.traceの適用例です。

user=> (require '[even-and-odd :as e])
nil
; 何もせずに関数を呼び出せば,通常通り関数の結果が返るだけ
user=> (e/even? 5)
false
user=> (require '[clojure.tools.trace :refer :all])
nil
; 関数even?とodd?をトレース
user=> (trace-vars e/even? e/odd?)
#'contrib-calendar.tools.trace/odd?
user=> (e/even? 5)
; 出力の各行にはユニークなIDがつけられ,関数の呼び出しと戻り値の対応を示す
TRACE t1895: (contrib-calendar.tools.trace/even? 5)
TRACE t1896: | (contrib-calendar.tools.trace/odd? 4)
TRACE t1897: | | (contrib-calendar.tools.trace/even? 3)
TRACE t1898: | | | (contrib-calendar.tools.trace/odd? 2)
TRACE t1899: | | | | (contrib-calendar.tools.trace/even? 1)
TRACE t1900: | | | | | (contrib-calendar.tools.trace/odd? 0)
TRACE t1900: | | | | | => false
TRACE t1899: | | | | => false
TRACE t1898: | | | => false
TRACE t1897: | | => false
TRACE t1896: | => false
TRACE t1895: => false
false
; untrace-varsでトレースを無効化
user=> (untrace-vars e/even? e/odd?)
#'contrib-calendar.tools.trace/odd?
user=> (e/even? 5)
false
user=> 

おわりに

今回は,シンプルなデバッグツールとしてtools.traceを紹介しました。
今回この記事を書くにあたって改めてtools.traceのコードを確認してみて,実装のやや甘い部分が散見されました。適用例で示したような使い方をする範囲では便利に使うことができますが,その他の機能を使用する場合には十分に注意が必要です。今後,tools.trace自体の完成度が上がること,また,別の高機能なトレースツールが現れることが期待されます。

次回は,引き続き僕athosがcore.matchについて書きます。