例外その2

今回はGaucheのtest*、test-start、test-endの簡易版を実装してみようという内容。
今までの知識を以ってすれば特に難しくはないが、簡易版test*であるmytestの実装でのthunkの話がポイントですかね。
手続きの評価を遅らせるために、関数適用ではなくラムダ抽象の形でとめておき、受け取った関数側で適用するわけやね。

(define *num-tests* 0)
(define *num-passes* 0)
(define *num-failures* 0)

;; test-startに相当
(define mytest-start
  (lambda (name)
    (print "Testing " name " ...")
    (set! *num-tests* 0)
    (set! *num-passes* 0)
    (set! *num-failures* 0)))

;; test-endに相当
(define mytest-end
  (lambda ()
    (print *num-tests* " tests, "
           *num-passes* " passed, "
           *num-failures* " failed.")))

;; test*に相当。thunkがポイント。
(define mytest
  (lambda (label expected thunk . options)
    (let-optionals* options ((cmp-fn equal?))
      (print "Testing " label " ...")
      (inc! *num-tests*)
      (let ((result (guard (e (else 'test-error))
                      (thunk))))  ; ここで評価
        (cond
         ((cmp-fn expected result) (inc! *num-passes*)
                                   (print " : OK"))
         (else (inc! *num-failures*)
               (print " : ERROR: expecting " expected ", but got " result)))))))

mytestで
これらを使うときはこんな感じ。

gosh> (mytest-start "hoge")
Testing hoge ...
0
gosh> (mytest "last-pair" '(baz) (lambda () (last-pair '(foo bar baz))))
Testing last-pair ...
 : OK
#<undef>
gosh> (mytest-end)
1 tests, 1 passed, 0 failed.
#<undef>

test-startとtest-endが何やってるか分からんかったけど、多分こんなことやってるんやろな。

訂正の訂正

「テスト」id:yagiey:20091229:1262112812でtest*はマクロかどうかに触れたときに、その根拠について訂正したけど、やっぱり訂正が間違ってたみたい。

まず、深いネストからerrorで投げたコンディションがguardで捕捉されることを確認。

gosh> (guard (e (else e))
        ((lambda ()
           ((lambda ()
              ((lambda () (error "error!!"))))))))
#<error "error!!">

うむ、深い関数適用で投げられたコンディションがちゃんと捕捉されているようだ。

例えば、次のような引数を1個とる手続きprocの評価について考えてみる。

(proc (another-proc arg1 arg2))

Schemeは評価する式が関数適用の場合は関数適用に先立ち全ての引数を評価する。
上のprocの評価ではprocの適用に先立ち(another-proc arg1 arg2)とprocの評価が行われる。さらにanother-procの適用に先立ってarg1とarg2とanother-procの評価が行われる。proc適用に先立つどれかの評価で発生した例外を補足するためには、

  1. another-procの適用をguardで包む。はじめに確認したみたいな感じ。
  2. procがthunkを受け取るように定義し、procが内部でguardする。

のどちらかをしなければならない。つまり、いずれにしてもguard(みたいなもの)で投げられたコンディションを捕捉しなければならない。
例えば前者はこう。

(proc (guard (e (else e))
        (another-proc arg1 arg2))

後者ならこんな感じか。

(define proc
  (lambda (thunk)
    (guard (e (else e))
      (thunk))))

test*はどっちでもない。やっぱり通常の手続きじゃないっぽいなぁ。