例外の発生は,Haskellを含む多く言語にとって,それまで行っていた計算の強制的な中断・終了を意味します。例外の発生による計算の中断は,並行処理,中でもSoftware Transactional Memory(STM)ではどのような意味を持つでしょうか? また,例外の発生による計算の中断をより安全に行うにはどうすればよいでしょうか?

 今回は,例外の「中断・終了処理」としての側面を説明します。

前回の補足

 2008年11月4日,GHC 6.10.1が公開されました(参考リンク)。これがGHCのメジャーバージョンアップである6.10の最初のリリースになります。GHCでは6.8以降,新しいメジャーバージョンのリリースの際には「.1」を付与するという番号規則を用いています(参考リンク)。

 WindowsやIntelプロセサ搭載Macに向けてインストーラが用意されています。他のいくつかのプラットフォームに対しても,ビルド済みのバイナリが提供されています。また,MacPortsなどのパッケージ・システムによる提供も行われています(参考リンク)。

 GHC 6.10.1では,多くの新機能が追加され,いくつかの点が変更されました(参考リンク)。このうち新しい例外機構については前回,並列GCについては第21回のコラム,データ並列Haskellのベクトル化機能については第24回のコラムでそれぞれ説明しています。

例外の発生による処理の中断

 並行処理での例外の発生について説明する前に,逐次的な処理での例外の発生について復習しておきましょう。

 前回は,「例外の発生」と「そこからの回復」の二つを一組にして説明しました。しかし、これらは必ず一対一に対応するとは限りません。他の多くの言語と同様に,Haskellの例外は処理の終了を意味するため,例外が捕捉される際に行われていたすべての処理が終了することになるからです。

 この事実は,「例外を発生させる」ことが,あらゆる計算処理を中断・終了させる手段になりうることを示しています。例えば,無限ループを以下のように例外によって終了させることができます。

module AsyncException where
import Control.Monad
import Data.IORef

foreverTest = do
    ref <- newIORef 0
    forever $ do
        var <- readIORef ref
        if var <= 10
          then do
              print var
              modifyIORef ref (+1)
          else error "varaible is greater than 10."

*AsyncException> foreverTest
0
1
2
3
4
5
6
7
8
9
10
*** Exception: varaible is greater than 10.

 foreverは無限ループを記述するための関数で,Control.Monadモジュールで定義されています。

forever     :: (Monad m) => m a -> m b
forever a   = a >> forever a

Prelude Control.Monad> forever $ print "inifinite loop."
"inifinite loop."
"inifinite loop."
"inifinite loop."
"inifinite loop."
"inifinite loop."
"inifinite loop."
"inifinite loop."
"inifinite loop."
"inifinite loop."
~ 略 ~

 foreverTestが例外を発生させなければ,「前の値に1を足した値を出力する」処理を以下のように延々と続けることになります。

foreverTest' = do
    ref <- newIORef 0
    forever $ do
        var <- readIORef ref
        print var
        modifyIORef ref (+1)

*AsyncException> foreverTest'
0
1
2
3
4
5
6
7
8
9
10
11
~ 略 ~
47264
47265
47266
47267
47268
47269
47270
~ 略 ~

 もっとも,例外をgotoやcall/ccのような大域脱出の手段として使うのは望ましいことではありません(参考リンク1参考リンク2)。たいていの場合,例外によって大域脱出を無理矢理行わなくても,再帰やmapM*などの高階関数を使うことで,大域脱出を使わないようにコードを書き直せます。一方で,例外の発生を「あらゆる処理を中断するための手段」とみなすことは,並行処理の環境においては重要な意味を持ってきます。