あどけない話

インターネットに関する技術的な話など

Haskellでの例外処理(その2)

Haskell での例外処理の続き。今日は例外を投げるよ!

throwIO

IO の中で、例外を投げるには throwIO を使います。

throwIO :: Exception e => e -> IO a

Exception型クラスのインスタンスを渡せばよさそうです。Control.Exceptionのマニュアルを読むと、Exception型クラスのインスタンスとして、IOException や ArithException があるのが分かります。

この中から、データ構成子が公開されているものを探してみましょう。ArithException は、データ構成子を公開していますね。その一つである、Overflow という例外を投げてみましょう。

> :m Control.Exception
Control.Exception> throwIO Overflow 
*** Exception: arithmetic overflow

投げられました。

データ構成子が公開されてないものは、throwIO できません。非公開のデータ構成子を投げられるのは、そのライブラリの作者のみです。

自分で例外を作る (安直編)

IOException であれば、自分で例外を作れます。ややこしいんですが、IOException と Haskell 2010 で定義されている IOError は、単なる別名の関係にあります。IOError を作るには、userError を使います。

userError :: String -> IOError

見ての通り、文字列を渡すだけですね。

Control.Exception> throwIO $ userError "My error"
*** Exception: user error (My error)

IOError は、データ構成子を公開していない抽象データ型なので、中身に触るには関数を使います。文字列を取り出すには、以下のようにします。

Control.Exception System.IO.Error> ioeGetErrorString $ userError "My error"
"My error"

実際には、catch する関数で、このような操作をやることになります。

自分で例外を作る (本格編)

Haskell の例外は拡張可能です。自分で型を定義して、例外の仲間に入れてもらうことができます。やり方は以下の通りです。これがどういう意味だか考えてはいけません。とにかく、こうするのです。

{-# LANGUAGE DeriveDataTypeable #-}

import Control.Exception as E
import Data.Typeable

data MyException = ThisException | ThatException deriving (Show, Typeable)

instance Exception MyException

厳密に捕捉する

昨日は、あらゆる例外を捕捉する ignore という関数を catch に渡していました。今日は、例外を選んで catch してみましょう。次のようなハンドラを定義します。

arithHandler :: ArithException -> IO ()
arithHandler e = putStrLn $ "Arith " ++ show e
    
ioHandler :: IOException -> IO ()
ioHandler e = putStrLn $ "IO " ++ show e

使ってみましょう。

> throwIO Overflow `E.catch` arithHandler `E.catch` ioHandler 
Arith arithmetic overflow
> throwIO (userError "My error") `E.catch` arithHandler `E.catch` ioHandler
IO user error (My error)

やったね! ところで、catch を連鎖するのは面倒なので、catches という関数も定義されています。

> throwIO Overflow `catches` [Handler arithHandler, Handler ioHandler]
Arith arithmetic overflow

第三の多相性

さて、catches のコードは気持ち悪くありませんか? 型 ArithException -> IO () と型 IOException -> IO () という異なる型が、同じリストの中に入ってますよ!

このように、別々の型を同じ型に見せかけるのが、Haskell の第三の多相性 ExistentialQuantification です。簡単に言うと、クラスベースのオブジェクト指向の継承みたいな多相性です。 ArithException も IOException も、SomeException として扱えます。詳しくは Haskellの多相性とかHaskell vs OOPを読んで下さいね。

おまけ

これで Haskellでの例外処理は、だいたい分かったはずです。後はモナド変換子を使った場合の例外処理ですが、これは 「lifted-base パッケージの Control.Exception.Lifted を使ってね」というぐらいでいいでしょう。