Feb 18 2012

schemeの継続を見ていたけど、大域脱出に用いる様は、モナドで途中で失敗する例に似てるよね. 具体的にはこんなイメージが湧いた.

(define-syntax >>=
    (syntax-rules ()
        ((_ x f)     (f x))
        ((_ x f g ...) (>>= (f x) g ...)) ))

計算の予定を順番に並べる. (>>= x f g ...) と書くと、これは(..(g (f x))..)に展開される. 左から読める方がいいよね.

記号は|>が良かったんだけど(F#ぽく)、Schemeで変数名に|は使えないというか特別な意味を 持つので、もうbindの記号にしちゃった.

(define 1+ (lambda(x) (+ x 1)))
(define 1- (lambda(x) (- x 1)))
(define sq (lambda(x) (* x x)))

例えば

gosh> (>>= 2 sq 1+)
5

途中で失敗するというのは、 その計算の列の途中に、例えば

逆数を求める手続き

gosh> (define (inv x) (if (zero? x) (fail 'inverse-error) (/ x)))
inv

引数が0の時、failを行う. failはグローバル変数

gosh> (define fail #f)
fail
gosh> (call/cc (lambda(cc) (set! fail cc) (>>= 3 1- inv sq)))
1/4
gosh> (call/cc (lambda(cc) (set! fail cc) (>>= 1 1- inv sq)))
inverse-error

というか、じゃあ、(call/cc ... も含めてマクロにするべきだった.

さっきの>>= を %>>= ということにして、

(define-syntax %>>=
    (syntax-rules ()
        ((_ x f)     (f x))
        ((_ x f g ...) (%>>= (f x) g ...)) ))

(define-syntax >>=
    (syntax-rules ()
        ((_ x ...)
            (begin
                (define fail #f)
                (call/cc (lambda (cc) (set! fail cc) (%>>= x ...)))) )))

毎回 大域変数 fail を define しちゃうのが、若干気持ち悪いけど

gosh> (>>= 3 inv sq)
1/9                
gosh> (>>= 0 inv sq)
inverse-error

おお、でけた


で、なんで考えたかと言うと、
別にこれができたからってモナドが畏怖に値するのには変わらないんだけど、 途中で失敗するかもしれないという例で使われるとき、失敗してるかのチェック を個別の関数でする必要がなく、bind関数がするという利点を挙げる. 今、自分が実験した例でも、そうなっている.