関数呼び出しを木構造で表示
SICPで関数の呼出を木構造で表示する問題がでてきたけど、これを手で書くのは大変。というわけで、Schemeでマクロを使って、呼出の木を自動で表示するのを作りました.slibのtraceと大体同じです。場合によっては、traceよりも見やすくなると思います.木で表示する部分はHaskellのdrawTreeを参考に作りました.
gaucheで動くことは確認しました.r5rs以外の手続きは, formatしか使っていないので、他の処理系でもそれさえあれば動くと思います.
定義マクロ
- tree-trace-on proc : procの追跡をonにする
- tree-trace-off proc : procの追跡をoffにする
- tree-trace (proc arg ...) : procの追跡を一時的にonにした状態でprocを呼ぶ.
サンプル
gosh> (define (fibonacci n) (if (< n 2) n (+ (fibonacci (- n 1)) (fibonacci (- n 2))))) fibonacci gosh> (tree-trace (fibonacci 5)) (fibonacci 5) = 5 | +-(fibonacci 4) = 3 | | | +-(fibonacci 3) = 2 | | | | | +-(fibonacci 2) = 1 | | | | | | | +-(fibonacci 1) = 1 | | | | | | | `-(fibonacci 0) = 0 | | | | | `-(fibonacci 1) = 1 | | | `-(fibonacci 2) = 1 | | | +-(fibonacci 1) = 1 | | | `-(fibonacci 0) = 0 | `-(fibonacci 3) = 2 | +-(fibonacci 2) = 1 | | | +-(fibonacci 1) = 1 | | | `-(fibonacci 0) = 0 | `-(fibonacci 1) = 1 5 gosh> (define (sub x y) (- x y)) sub gosh> (define (mult x y) (* x y)) mult gosh> (define (fact n) (if (= n 0) 1 (mult n (fact (sub n 1))))) fact gosh> (tree-trace-on sub) #<closure #<identifier user#alt-proc>> gosh> (tree-trace-on mult) #<closure #<identifier user#alt-proc>> gosh> (tree-trace-on fact) #<closure #<identifier user#alt-proc>> gosh> (fact 5) (fact 5) = 120 | +-(sub 5 1) = 4 | +-(fact 4) = 24 | | | +-(sub 4 1) = 3 | | | +-(fact 3) = 6 | | | | | +-(sub 3 1) = 2 | | | | | +-(fact 2) = 2 | | | | | | | +-(sub 2 1) = 1 | | | | | | | +-(fact 1) = 1 | | | | | | | | | +-(sub 1 1) = 0 | | | | | | | | | +-(fact 0) = 1 | | | | | | | | | `-(mult 1 1) = 1 | | | | | | | `-(mult 2 1) = 2 | | | | | `-(mult 3 2) = 6 | | | `-(mult 4 6) = 24 | `-(mult 5 24) = 120 120
ソース
;; Stack for tree (define tree-trace:node-stack '()) (define (tree-trace:push! x) (set! tree-trace:node-stack (cons x tree-trace:node-stack))) (define (tree-trace:top) (car tree-trace:node-stack)) (define (tree-trace:pop!) (define t (tree-trace:top)) (set! tree-trace:node-stack (cdr tree-trace:node-stack)) t) (define (tree-trace:null?) (null? tree-trace:node-stack)) ;; alist key: alternative procedure val: original procedure (define tree-trace:proc-alist '()) (define (tree-trace:add-trace-entry alt-proc orig-proc) (set! tree-trace:proc-alist (cons (cons alt-proc orig-proc) tree-trace:proc-alist))) (define (tree-trace:remove-trace-entry alt-proc) (let loop ((l tree-trace:proc-alist)) (cond ((null? l) (error "It does not traced")) ((eq? alt-proc (caar l)) (cdar l)) (else (loop (cdr l)))))) ;; tree (define (tree-trace:make-tree label nodes) (cons label nodes)) (define (tree-trace:add-child! tree child) (set-cdr! tree (append (cdr tree) (list child)))) (define (tree-trace:get-label tree) (car tree)) (define (tree-trace:set-label! tree label) (set-car! tree label)) ;; utility (define (tree-trace:print . args) (for-each display args) (newline)) (define (tree-trace:string-join l . args) (define delim (if (null? args) "" (car args))) (define s (make-string 0)) (if (not (null? l)) (begin (set! s (format "~a" (car l))) (for-each (lambda (t) (set! s (string-append s (format "~a~a" delim t)))) (cdr l)))) s) ;; make string of procedure call (define (tree-trace:proc-call-string f args) (string-append "(" (tree-trace:string-join (map (lambda (x) (format "~a" x)) (cons f args)) " ") ")")) ;; trace procedure (define-syntax tree-trace-on (syntax-rules () ((_ proc) (begin (if (not (and (symbol? 'proc) (procedure? proc))) (error "It's not a procedure's variable.")) (let* ((orig-proc proc) (alt-proc (lambda args (tree-trace:push! (tree-trace:make-tree (tree-trace:proc-call-string 'proc args) '())) (let* ((v (apply orig-proc args)) (t (tree-trace:pop!)) (pre-label (tree-trace:get-label t))) (tree-trace:set-label! t (format "~a = ~a" pre-label v)) (if (tree-trace:null?) (tree-trace:print-tree t) (tree-trace:add-child! (tree-trace:top) t)) v)))) (tree-trace:add-trace-entry alt-proc orig-proc) (set! proc alt-proc)))))) ;; untrace procedure (define-syntax tree-trace-off (syntax-rules () ((_ proc) (begin (if (not (and (symbol? 'f) (procedure? proc))) (error "It's not a procedure's variable.")) (set! proc (tree-trace:remove-trace-entry proc)))))) ;; call & trace procedure (define-syntax tree-trace (syntax-rules () ((_ (f arg ...)) (begin (tree-trace-on f) (let ((v (f arg ...))) (tree-trace-off f) v))))) (define (tree-trace:shift first rest l) (cond ((null? l) '()) ((null? (cdr l)) (list (string-append first (car l)))) (else (cons (string-append first (car l)) (map (lambda (t) (string-append rest t)) (cdr l)))))) (define (tree-trace:print-tree tree) (for-each tree-trace:print (tree-trace:tree->strings tree))) (define (tree-trace:tree->strings tree) (cons (car tree) (tree-trace:forest->strings (cdr tree)))) (define (tree-trace:forest->strings l) (cond ((null? l) '()) ((null? (cdr l)) (cons "|" (tree-trace:shift "`-" " " (tree-trace:tree->strings (car l))))) (else (cons "|" (append (tree-trace:shift "+-" "| " (tree-trace:tree->strings (car l))) (tree-trace:forest->strings (cdr l)))))))