Continuation Monad (3)

[Haskell] Continuation Monad (2) [id:yts:20050103#p1] の続き。

13.3 Tree matching

まずTree型の定義をしておく。

data Tree a = Tree [Tree a] | Leaf a deriving (Eq, Show)
car (Tree (x:xs)) = x
cdr (Tree (x:xs)) = Tree xs
isNull (Tree []) = True
isNull _ = False
isPair (Tree (x:xs)) = True
isPair _ = False

ついでにSchemeのcondを真似してみる。

cond ((True, w):xs) = w
cond ((_, w):xs) = cond xs
Example 1
(define tree->generator
  (lambda (tree)
    (let ((caller '*))
      (letrec
          ((generate-leaves
            (lambda ()
              (let loop ((tree tree))
                (cond ((null? tree) 'skip)
                      ((pair? tree)
                       (loop (car tree))
                       (loop (cdr tree)))
                      (else
                       (call/cc
                        (lambda (rest-of-tree)
                          (set! generate-leaves
                            (lambda ()
                              (rest-of-tree 'resume)))
                          (caller tree))))))
              (caller '()))))
        (lambda ()
          (call/cc
           (lambda (k)
             (set! caller k)
             (generate-leaves))))))))

generate_leaves は (set! generate-leavesa ...) で書き換えられているので、STRefにする。関数として呼び出すときはあらかじめreadSTRefすることになる。
問題は、generate_leavesの定義で自分自身を参照しているが、モナドの束縛なので

generate_leaves <- ... generate_leaves ...

とは書けないことだ。ContT(Contも)はMonadFixのinstanceではないので、mfixというわけにもいかない。

generate_leavesは(lambda () ...)なので、STRef s (ContT r (ST s) (Tree a))という型になるだろう。ContT r (ST s)に関する副作用はreadSTRefしたあと手動で走らせることで生じさせることになる。つまりgenerate_leavesをバインドするときにはContT r (ST s)に関する副作用は発生しない。STモナドのmfix(fixST)で十分だ。

tree_generator :: Tree a -> ContT r (ST s) (ContT r (ST s) (Tree a))
tree_generator tree = 
    do  caller <- lift $ newSTRef undefined
        generate_leaves <- lift $ fixST (\generate_leaves ->
            do  newSTRef (
                  do  fix (\loop tree ->
                        cond [(isNull tree, return (error "skip"))
                            , (isPair tree, loop (car tree) >> loop (cdr tree))
                            , (otherwise, callCC (\rest_of_tree ->
                                  do  lift $ writeSTRef generate_leaves (rest_of_tree (error "resume"))
                                      c <- lift $ readSTRef caller
                                      c tree))]) tree
                      c <- lift $ readSTRef caller
                      c (Tree [])
                  ))
        return (
          do  callCC (\k ->
                do  lift $ writeSTRef caller k
                    g <- lift $ readSTRef generate_leaves
                    g) )
Example 2
(define same-fringe?
  (lambda (tree1 tree2)
    (let ((gen1 (tree->generator tree1))
          (gen2 (tree->generator tree2)))
      (let loop ()
        (let ((leaf1 (gen1))
              (leaf2 (gen2)))
          (if (eqv? leaf1 leaf2)
              (if (null? leaf1) #t (loop))
              #f))))))
same_fringe :: Eq a => Tree a -> Tree a -> ContT r (ST s) Bool
same_fringe tree1 tree2 = 
    do  gen1 <- tree_generator tree1
        gen2 <- tree_generator tree2
        fix (\loop -> do
            leaf1 <- gen1
            leaf2 <- gen2
            if leaf1 == leaf2 then
                if isNull leaf1 then return True else loop
              else return False)
f07 = runST (flip runContT return $ 
      same_fringe (Tree [Leaf 1, Tree [Leaf 2, Leaf 3]])
        (Tree [Tree [Leaf 1, Leaf 2], Leaf 3]))
f08 = runST (flip runContT return $
      same_fringe (Tree [Leaf 1, Leaf 2, Leaf 3]) (Tree [Leaf 1, Tree [Leaf 3, Leaf 2]]))

Schemeのletrecをどう扱うかが難しい点だ。奥が深い。

><

結局STモナドの上で動いているのだから、strictであっても、継続を使うよりもイテレーターを保持しているgeneratorを作った方がわかりやすいような。Schemerはイテレータよりも継続を選ぶのだろうか。