StateモナドをSchemeで

id:syd_sydさんにモナドについて教わって、ようやくStateモナドを理解できたのでとりあえずSchemeで書いてみた。出力(?)と状態の組は多値で表現。

(define (>>= m f)
  (lambda (s0)
    (receive (a s1) (run-state m s0)
      (run-state (f a) s1))))

(define (run-state m s)
  (m s))

(define (return a)
  (lambda (s)
    (values a s)))

(define (get)
  (lambda (s)
    (values s s)))

(define (put s)
  (lambda (_)
    (values #f s)))

(define-syntax doM
  (syntax-rules (<-)
    [(_ (x <- m) rest ...)
     (>>= m (lambda (x) (doM rest ...)))]
    [(_ s0 s1 rest ...)
     (>>= s0 (lambda (_) (doM s1 rest ...)))]
    [(_ s) s]))

(>>=* m f1 f2 ...) = (... (>>= (>>= m f1) f2) ...) になるような>>=*とかも作ったけど、doMを書いたらいらなくなったのでカット。

実行は下のようになる。

gosh> (run-state
        (doM (x <- (get))
             (put (+ x 100))
             (x <- (get))
             (put (+ x 10))
             (x <- (get))
             (put (+ x 1)))
        0)
#f
111
gosh>

結局何につまづいていたかというと、getやputはデータを出し入れする先を明示していないのに処理ができてしまうという点でした。 Stateモナドでのひと続きの計算中では、裏で管理される状態は常に1つなので陽に与える必要がない、というのが直観的な理解の仕方のようです。

それから、HaskellでのStateモナドの解説ではgetが get = \s -> ... ではなく、get s = ... と定義してあったりして、カリー化脳が出来上がってない身にはこれも理解の妨げになっていたのかも。ラムダ式を使った方が「クロージャ作ってます」っていうのがよく分かっていいと思うんだけどなぁ。