Continuation Monad (4)

一応最後まで書いておく。でも随分前に書いたのでどう考えたのかは忘れてしまった。

13.4 Coroutines

まずcoroutine macroを定義する。

(define-macro coroutine
  (lambda (x . body)
    `(letrec ((+local-control-state
               (lambda (,x) ,@body))
              (resume
               (lambda (c v)
                 (call/cc
                  (lambda (k)
                    (set! +local-control-state k)
                    (c v))))))
       (lambda (v)
         (+local-control-state v)))))

Haskellはlazyなので、strictなSchemeではマクロでないとできないことが関数でできる。
condやifは簡単だ。しかしこのマクロの場合はそれほど簡単ではない。継続を呼び出すときに使うためのresumeが"+local-control-state"を参照しているためだ。

(define make-matcher-coroutine
  (lambda (tree-cor-1 tree-cor-2)
    (coroutine dont-need-an-init-arg
      (let loop ()
        (let ((leaf1 (resume tree-cor-1 'get-a-leaf))
              (leaf2 (resume tree-cor-2 'get-a-leaf)))
          (if (eqv? leaf1 leaf2)
              (if (null? leaf1) #t (loop))
              #f))))))

上の二つのresumeは、このcontine macroの呼び出しに固有の"+local-control-state"を呼び出す必要がある。
隠れて"+local-control-state"をresumeに渡すためにReaderモナドを使うと、以下のようになる*1

coroutine :: (t -> ReaderT (STRef s (t -> ContT r (ST s) a)) (ContT r (ST s)) a)
             -> ContT r2 (ST s) (t -> ContT r (ST s) a)
coroutine lambody 
  = do  local_control_state <- lift $ fixST (\local_control_state -> 
          newSTRef $ \init_arg -> 
              do  runReaderT (lambody init_arg) local_control_state)
        return $ \init_arg -> 
            do  lcs <- lift $ readSTRef local_control_state
                lcs init_arg
resume :: (a -> ContT r (ST s) c) -> a 
           -> ReaderT (STRef s (c -> ContT r (ST s) b)) (ContT r (ST s)) c
resume c v = 
  do  local_control_state <- ask
      lift $ callCC (\k -> 
        do  lift $ writeSTRef local_control_state k
            c v)

このように、resumeに引数を渡すこととを避けられた。

make_matcher_coroutine tree_cor_1 tree_cor_2
    = do  coroutine $ \_ -> 
            fix (\loop -> 
              do  leaf1 <- resume tree_cor_1 (error "get_a_leaf")
                  leaf2 <- resume tree_cor_2 (error "get_a_leaf")
                  if leaf1 == leaf2 then
                      if  isNull leaf1 then return True else loop
                    else return False)
(define make-leaf-gen-coroutine
  (lambda (tree matcher-cor)
    (coroutine dont-need-an-init-arg
      (let loop ((tree tree))
        (cond ((null? tree) 'skip)
              ((pair? tree)
               (loop (car tree))
               (loop (cdr tree)))
              (else
               (resume matcher-cor tree))))
      (resume matcher-cor '()))))
make_leaf_gen_coroutine tree matcher_cor
    = do  coroutine $ \_ ->
            do  fix (\loop tree ->
                  do  cond [(isNull tree, return (error "skip"))
                          , (isPair tree, loop (car tree) >> loop (cdr tree))
                          , (otherwise  , resume matcher_cor tree >> return (error "next"))]) tree
                resume matcher_cor (Tree []) 
                fail "impossible"

次はsame-fringe?だ。
ここでletrecをこう定義してみる。この定義は、中でrunContTを実行しているので、継続を保存しない。つまり、letrecの中でcall/ccを呼んだりする使いかたに対しては正常に(schemeで期待されるように)動作しない。しかし、一般的な使用では問題がない。

letrec f = lift $ mfix $ \x -> runContT (f x) return

Unfortunately, Scheme's letrec can resolve mutually recursive references amongst the lexical variables it introduces only if such variable references are wrapped inside a lambda. And so we write:

とあって、Schemeでも matcher-cor を lambda (v) (matcher-cor v) と書いて名前解決を遅らせなければならないように、ここでreferenceを挟むか、runContTしておかないと、無限ループになる。ここではletrecの中でrunContTを走らせてしまうことにした。

(define same-fringe?
  (lambda (tree1 tree2)
    (letrec ((tree-cor-1
              (make-leaf-gen-coroutine
               tree1
               (lambda (v) (matcher-cor v))))
             (tree-cor-2
              (make-leaf-gen-coroutine
               tree2
               (lambda (v) (matcher-cor v))))
             (matcher-cor
              (make-matcher-coroutine
               (lambda (v) (tree-cor-1 v))
               (lambda (v) (tree-cor-2 v)))))
      (matcher-cor 'start-ball-rolling))))
same_fringe' tree1 tree2
    = do  (_, _, matcher_cor) <- letrec (\ ~(tree_cor_1, tree_cor_2, matcher_cor) ->
            do  t1 <- make_leaf_gen_coroutine tree1 matcher_cor
                t2 <- make_leaf_gen_coroutine tree2 matcher_cor
                mc <- make_matcher_coroutine tree_cor_1 tree_cor_2
                return (t1, t2, mc ))
          matcher_cor (error "start-ball-rolling")

テスト

d01 = Tree [Leaf 1, Leaf 2, Leaf 3]
d02 = Tree [Leaf 1, Tree [Leaf 3, Leaf 2]]

f10 = runST (flip runContT return $ same_fringe' d01 d02)
f11 = runST (flip runContT return $ same_fringe' d01 d01)

>

*1:implicit paramterでも良い