xyzzy で trace を使ってみる

ウェブで lisp の入門とか講義テキストとかを見ていると、 trace という関数で呼び出しの引数と戻り値の履歴を確認できるようだ。
これは再帰を勉強しているときはめちゃめちゃ役に立つに違いない。
でも xyzzy には trace 関数がないみたいだ。じゃあ作ろうかと思っても、マクロ初心者のぼくには明らかに無理だ。
誰か作ってる人いないかなと探してみると・・・発見。ああ、あなたは神だ。


trace を使うには、

  1. encap をインストールする
    netinstaller でサクっと encap を入れる*1
  2. trace.l をもらってくる
    さっきの神のページの使用例にあるので、これをコピってきて load-path の通ったところに置く。

の2段階が必要みたいだ。

入ったら早速テストしてみる。
練習でやった一番簡単な再帰

(defun my-fact (n)
  (if (= n 0) 1
    (* n (my-fact (1- n)))))

コレ↑でやってみる。

(trace my-fact) ;; my-fact を trace の対象に

(my-fact 5)     ;; 普通に C-j すると下記が "*Trace Output*"バッファに出力される
 Calling (my-fact 5)
  Calling (my-fact 4)
   Calling (my-fact 3)
    Calling (my-fact 2)
     Calling (my-fact 1)
      Calling (my-fact 0)
      my-fact returned 1
     my-fact returned 1
    my-fact returned 2
   my-fact returned 6
  my-fact returned 24
 my-fact returned 120
120

(untrace my-fact)   ;; 使い終わったら trace の対象から外しておく

(trace)             ;; 引数なしの (trace) は現在の trace 対象を返す

おお、いい感じだ。すばらしい。

ただ、今は練習中なので、いちいち *Trace Output*バッファに切り替えて確認するのがちょっとめんどくさいかな。
なので、C-j したらそのまま今のバッファに出力するように、trace-encap をちょこっとだけ変えさせてもらった。

;; mod
(defun trace-encap (func)
  (unless (encapsulated-p func 'traced-function)
    (encapsulate func 'traced-function
                 `(
;削除             ;(ed::setup-trace-output-buffer)
                   (setq *trace-depth* (1+ *trace-depth*))
;変更             ;(format *error-output* "~ACalling ~S~%" (make-sequence 'string *trace-depth* :initial-element #\SPC) (cons ',func argument-list))
                   (format t "~ACalling ~S~%" (make-sequence 'string *trace-depth* :initial-element #\SPC) (cons ',func argument-list))
                   (let ((#1=#:result (multiple-value-list (apply basic-definition argument-list))))
;変更               ;(format *error-output* "~A~S returned~{ ~A~}~%" (make-sequence 'string *trace-depth* :initial-element #\SPC) ',func #1#)
                     (format t "~A~S returned~{ ~A~}~%" (make-sequence 'string *trace-depth* :initial-element #\SPC) ',func #1#)
                     (setq *trace-depth* (1- *trace-depth*))
                     (values-list #1#))))
    (push func *trace-function-list*)
    func))

本当は「*scratch*バッファで呼んだとき以外は *Trace Output* バッファに出力する」ような関数を作ったほうがよさそうだけど、まあ今はこれでいいや。


trace のおかげで、再帰もずいぶん理解しやすくなった。それに、この encap.l を使えばいろいろ面白いことができそう。
がんばれば edebug も作れるかも。

*1:netinstaller --> kia's website の行で Enter --> encap の行で i を押す