(2017.6.11) 新規作成。
Haskell にも「例外」(exception) があります。Java や C#, C++ のような try-catch-finally (C++ には finally はありませんが.) といった専用の構文ではなく, 関数で取扱います。
これまでは, base パッケージの Control.Exception
モジュールを使っていました。最近, これまでの例外の難点を解消した safe-exceptions パッケージが出ています。新しいコードでは, このパッケージを使うようにします。
型が IO に限定されず、非同期例外にも対応している。
# cabal install --global --dry-run safe-exceptions Resolving dependencies... In order, the following would be installed (use -v for more details): transformers-compat-0.5.1.4 exceptions-0.8.3 safe-exceptions-0.1.4.0
優れた解説: Asynchronous Exception Handling in Haskell
Exceptions Best Practices in Haskell
例外が発生したときに捕捉するには, catch
関数を使います。Java, C++ などでの try-catch 構文に相当します.
例外型は, Exception
型クラスのインスタンスになります。IOException
, SomeException
など.
catch :: (MonadCatch m, Exception e) => m a -- 実行する本体 -> (e -> m a) -- 例外ハンドラ -> m a -- Defined in ‘Control.Exception.Safe’
現代では, 例外を送出 (throw) するかどうかは, IOモナドかどうか, ではありません。例えば, 純粋関数に見える head
関数も例外を投げます。なので, catch
でも、本体の戻り値に IO
を仮定しません。
catch
は, 本体のなかで例外が発生すると, そこで実行を中断し, 例外ハンドラを呼び出す。例外が発生した場合の catch
の戻り値は, ハンドラの戻り値。
catchIO
は IOException
型の例外を受けるための特殊版。
catchIO :: MonadCatch m => m a -> (IOException -> m a) -> m a
次の例は, 例外を投げて, 捕捉します。
異なる例外型が上がってくる場合は, catches
を使います。
try
関数は, Either
モナドで返します。
try :: (MonadCatch m, Exception e) => m a -> m (Either e a) -- Defined in ‘Control.Exception.Safe’
例外の形で失敗するかもしれない文を受けて, 分岐で判定します。失敗した (例外) 場合が Left です。
Haskell にもfinally
関数はあるが、大抵 finally
を使いたいのはリソースの解放を確実にするため。このような場合には, 便利関数として bracket
関数が使える.
便利関数は複雑な状況への対応なども注意深く実装されているため、自分で似たコードを実装せず、積極的に利用しよう。
bracket
関数は, Control.Exception.Safe モジュール (safe-exceptions パッケージ) で定義されている.
bracket
の型はこうなっている;
bracket :: MonadMask m => m a -- リソースハンドルを返す関数 -> (a -> m b) -- リソースを解放する関数. 必ず実行される -> (a -> m c) -- 本体 -> m c -- Defined in ‘Control.Exception.Safe’
ただし, MonadMask
型クラスは, Control.Monad.Catch.MonadMask
です。
bracket
の第3引数, 本体の部分で例外が発生した場合は、解放する関数を実行したうえで、例外が上がる.
次は, bracket
の使用例。hIsEOF
で検査せず, わざと例外が発生するようにしている。ハンドルが閉じられてから、例外が再送出される。
せっかくなので、bracket
の定義を見ておこう. mask
関数で, 非同期例外をいったん保留にしている。
昔の The Haskell 98 (Revised) Report では、次の定義になっていました。IO
モナドを仮定し、非同期例外への対応もありませんでした。
mask
関数の型はこう;
Control.Monad.Catch.mask :: MonadMask m => ((forall a. m a -> m a) -> m b) -> m b