(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