Prev: Writer モナド TOC: 目次 Next: Part III - イントロダクション

Continuationモナド


概観

計算のタイプ: 中断,再開が可能な計算
束縛の戦略: 関数をモナド値に束縛すると新しい継続が生成され,その継続は その関数をそのモナド計算の継続として使います.
利用場面: 複雑な制御機構,エラー処理,コルーチンの生成
ゼロおよびプラス: なし
型の例: Cont r a

動機

Continuation モナドの濫用は理解や保守が不可能なコードをつくり出す 可能性があります.

Continuation モナドを使う前に継続渡しスタイル(CPS)について 確実に理解しているか,自身の設計課題について継続が最良のソリューション なのかを確認してください.他の言語では継続が要求されるような多くの アルゴリズムにおいて,Haskell では遅延評価意味論のおかげで,継続を 必要としません.

継続は計算のこの先(future)を表現しており,これは 中間結果から最終結果への関数として表現されます.継続渡しスタイル では,計算は入れ子になった継続のならびから構築され,最終結果 を生成する最終継続(多くの場合 id)で終了します. 継続は計算のこの先を表現する関数ですから,継続関数を操作することで 複雑な計算のこの先を操作することができます.たとえば,途中での 計算への割込みや,計算の一部の中断,復帰,計算の交互実行などです. Continutation モナドは CPS をモナドの構造に適合させたものです.

定義

newtype Cont r a = Cont { runCont :: ((a -> r) -> r) } -- r は計算全体の最終の型
  
instance Monad (Cont r) where 
    return a       = Cont $ \k -> k a                       -- i.e. return a = \k -> k a 
    (Cont c) >>= f = Cont $ \k -> c (\a -> runCont (f a) k) -- i.e. c >>= f = \k -> c (\a -> f a k) 

Continuation モナドは継続渡しスタイルの計算を表現しています. Cont r a は最終の型が r で,CPS 内部の中間の型 a を生成する CPS 計算です.

return 関数は単純に値を次へ渡すだけの継続を 生成します.>>= 演算子は継続連鎖のなかに束縛された関数を 追加します.

class (Monad m) => MonadCont m where 
    callCC :: ((a -> m b) -> m a) -> m a 
 
instance MonadCont (Cont r) where 
    callCC f = Cont $ \k -> runCont (f (\a -> Cont $ \_ -> k a)) k

MonadCont クラスは callCC 関数を提供します. この関数は,Continuation モナド用の脱出継続機構を 提供するものです.脱出継続は現在の計算を中断し,即座に値を返します. これにより,Error モナドでの throwError および catchError に類似した効果が得られます.

callCC は現在の継続(Current Continuation)を引数として関数を 呼びます(それゆえ,CallCCという名があります). callCC を使うときの標準的なイディオムは,λ式を与えて 継続に名前をつけるというものです.そうすると,その名前のついた継続を スコープ内のあらゆる場所から呼ぶことで,それが入れ子の計算のどんなに 深いところからでも,脱出できます.

callCC によって提供される脱出機構以外にも,Continuation モナドでは別のより強力な継続の操作を実装できます.しかしながら, これらの機構は使用法がかなり特殊で,濫用すると簡単コードがわかりにくい ものになってしまします.それで,ここではこれらをとりあげることは しません.

この例は脱出継続の機能の感覚を示すものです. 例の関数は,脱出継続を使って,整数上の複雑な変換を実行するものです.

example18.hs で使えるコード
{- Continuation モナドを使って,コードブロックからの「脱出」を行います
   この関数は以下のような処理の複雑な制御構造を実装します

   Input (n)       Output                       List Shown
   =========       ======                       ==========
   0-9             n                            none
   10-199          number of digits in (n/2)    digits of (n/2)
   200-19999       n                            digits of (n/2)
   20000-1999999   (n/2) backwards              none
   >= 2000000      sum of digits of (n/2)       digits of (n/2)
-}  
fun :: Int -> String
fun n = (`runCont` id) $ do
        str <- callCC $ \exit1 -> do                        -- "exit1" を定義
          when (n < 10) (exit1 (show n))
          let ns = map digitToInt (show (n `div` 2))
          n' <- callCC $ \exit2 -> do                       -- "exit2" を定義
            when ((length ns) < 3) (exit2 (length ns))
            when ((length ns) < 5) (exit2 n)
            when ((length ns) < 7) $ do let ns' = map intToDigit (reverse ns)
                                        exit1 (dropWhile (=='0') ns')  --escape 2 levels
            return $ sum ns
          return $ "(ns = " ++ (show ns) ++ ") " ++ (show n')
        return $ "Answer: " ++ str

Prev: Writer モナド TOC: 目次 Next: Part III - イントロダクション