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 - イントロダクション