例外について(Parallel and Concurrent Programming in Haskell Chapter 8)
英語の原文はこちらのページで読める
http://chimera.labs.oreilly.com/books/1230000000929/ch08.html#sec_exceptions
前後に非同期処理の話があるがそれは別にまとめることにする
haskellでは例外に関して特別な構文などを言語レベルで用意してはいない
他の関数と同じく、例外についてもライブラリに含まれる関数の一つである
そのおかげで高階関数などを用いた抽象化を例外にも施すことができる
例外を投げる関数は以下
throw :: Exception e => e -> a
Exception型クラスのinstanceになっている型をとり、制約なく任意の型を返す
任意の型を返すということはどこでも使えるということ
Exception型クラスのinstanceとするにはTypeableとShowのinstanceであればよい
具体例はErrorCallでこのようになっている
newtype ErrorCall = ErrorCall String deriving (Typeable) instance Show ErrorCall where { ... } instance Exception ErrorCall
よく使われるerrorという関数はErrorCallを用いて以下のように定義されている
error :: String -> a error s = throw (ErrorCall s)
IOの中ではSystem.IO.Errorで定義されたIOExceptionが使われる
IOの中で例外をキャッチするにはcatchを使う
catch :: Exception e => IO a -> (e -> IO a) -> IO a
実行したいIOアクションと例外が起こった際のハンドラーをとってIOを返す
どの例外を捕捉するかはハンドラーに渡す引数eで決まる
ghciでやってみる
$ ghci GHCi, version 7.8.3: http://www.haskell.org/ghc/ :? for help Loading package ghc-prim ... linking ... done. Loading package integer-gmp ... linking ... done. Loading package base ... linking ... done. Prelude> import Control.Exception Prelude Control.Exception> import Data.Typeable Prelude Control.Exception Data.Typeable> :set -XDeriveDataTypeable Prelude Control.Exception Data.Typeable> data MyEx = MyEx deriving (Show, Typeable) Prelude Control.Exception Data.Typeable> instance Exception MyEx Prelude Control.Exception Data.Typeable> throw MyEx *** Exception: MyEx
自作の例外を作成してthrowした結果
XDeriveDataTypeableはTypeableを自動で導出するために必要なオプションである
catchしてみる
Prelude Control.Exception Data.Typeable> throw MyEx `catch` \e -> print e <interactive>:8:12: No instance for (Exception a0) arising from a use of ‘catch’ The type variable ‘a0’ is ambiguous Note: there are several potential instances: instance Exception MyEx -- Defined at <interactive>:6:10 instance Exception ArithException -- Defined in ‘GHC.Exception’ instance Exception ErrorCall -- Defined in ‘GHC.Exception’ ...plus 8 others In the expression: throw MyEx `catch` \ e -> print e In an equation for ‘it’: it = throw MyEx `catch` \ e -> print e
エラーになった。これはハンドラーに渡す例外を一意に決めていないためである
なので以下のようにeの型を指定する
Prelude Control.Exception Data.Typeable> throw MyEx `catch` \e -> print (e :: MyEx) MyEx Prelude Control.Exception Data.Typeable> throw MyEx `catch` \e -> print (e :: SomeException) MyEx Prelude Control.Exception Data.Typeable> throw MyEx `catch` \e -> print (e :: ArithException) *** Exception: MyEx
e :: MyExとすればそのままキャッチできる
SomeExceptionはすべての例外をキャッチできる型であり、これでもMyExがキャッチできた
ArithExceptionを指定した場合はMyExはスルーされて例外が表示されている
SomeExceptionの使いどころはデバッグ時や例外を一度受けて再送出したい場合など
例外処理をするためにいくつの関数が用意されている
try :: Exception e => IO a -> IO (Either e a)
tryは一つ目に指定したIOで例外が起きるとLeftの値として返してくれる
onException :: IO a -> IO b -> IO a
onExceptionは一つ目のIOで例外が起きた場合に2つ目のアクションを実行して再度例外を投げなおす
throwIO :: Exception e => e -> IO a
throwのIO内で使うバージョン
throwとの違いは実行順序がプログラムの通りとなることである
throwの場合はその他のアクションとの実行順序が入れ替わる可能性があるため、IO内ではthrowIOを使うべきである
bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c
bracketはリソースの取得解放によく使う
一つ目のIOでリソースの取得など初期化処理を行う
その返り値が2つ目、3つ目のIOの引数aとなっている
2つ目のIOは3つ目のIO実行後または例外発生時に実行されるため、ここでリソースの解放処理を行う
3つ目が実際に行いたい処理自体を書くところである
ファイルのopen, closeがわかりやすい
おそらくwithFileはこのようなことをやっているのだと思われる
bracket (openFile "hoge") (\h -> hClose h) (\h -> writeFile h WriteMode $ ...)
ちなみにbracketの単純な形はここまでに出てきた関数で定義できる
bracket before after during = do a <- before c <- during a `onException` after a after a return c
bracketの特殊バージョンで初期化処理は要らないが最後に特定の処理を必ず入れたい場合
finally :: IO a -> IO b -> IO a finally io after = do io `onException` after after
- 作者: Simon Marlow
- 出版社/メーカー: O'Reilly Media
- 発売日: 2013/07/12
- メディア: Kindle版
- この商品を含むブログ (2件) を見る
- 作者: Simon Marlow,山下伸夫,山本和彦,田中英行
- 出版社/メーカー: オライリージャパン
- 発売日: 2014/08/21
- メディア: 大型本
- この商品を含むブログ (2件) を見る