Prev: Maybe モナド TOC: 目次 Next: List モナド

Error モナド


概観

計算のタイプ: 失敗あるいは例外を投げる計算
束縛戦略: 失敗はその失敗が発生した理由/場所に関する情報を記録する。 失敗値は束縛された関数をバイパスし、それ以外の値は束縛された関数の 入力として使われます。
利用場面: 失敗あるいはエラー処理を構造化する例外処理をつかう関数のならびから 計算を構築する。
ゼロとプラス: ゼロは空のエラーにより表現され、プラス演算は最初の引数が失敗すれば、 2つめの引数を実行します。
型の例: Either String a

動機

Error モナド(Exception モナドともいいます)は、 束縛された関数をバイパスすることで、発生したところから、 それを処理する処理するところへ例外を投げることができる 計算を、合成する戦略を内包しています。

MonadError クラスはエラー情報の型とモナド型構築子 によってパラメータ化されています。Either String を エラー内容の記述が文字列形式であるエラーモナド用のモナド型構築子 として使うのが共通しています。そのケースやその他のよくあるケースでは 結果のモナドはすでに MonadError クラスのインスタンスとして 定義されています。自分用のエラーの型を定義することもできますし、 Either String あるいは Either IOError 以外のモナド型構築子を使えます。 これらのケースでは、Error および/あるいは MonadError クラスのインスタンスを明示的に定義する必要が あります。

定義

以下の MonadError クラスの定義は複数パラメータの型クラスと funDep という標準の Haskell 98 にはない言語拡張をつかっています。 MonadError クラスの利点を得るのにこれを理解する必要はありません。

class Error a where
    noMsg :: a
    strMsg :: String -> a

class (Monad m) => MonadError e m | m -> e where
    throwError :: e -> m a
    catchError :: m a -> (e -> m a) -> m a

throwError はモナド計算内で例外処理を始めるために 使われます。catchError は直前のエラーを処理し、通常の 実行に復帰するためのハンドラ関数を提供します。 よくつかわれるイディオムは、

do { action1; action2; action3 } `catchError` handler 
です。ここで、action 関数は throwError を 呼ぶことができます。handler およびその do ブロックは 同じ返り値型をもたなければなりません。

MonadError クラスのインスタンスとして定義された Either e 型構築子は直截的なものです。以下の 慣習では、Left はエラー値に使い、Right は 非エラー値(正しい値)に使います。

instance MonadError (Either e) where 
    throwError = Left 
    (Left e) `catchError` handler = handler e 
    a        `catchError` _       = a 

次の例は、ErrorMonadthrowError および catchError の例外機構を使ってカスタマイズした Error データ型の使い方を示したものです。 この例は16進数をパースし、不正な文字に出会うと例外を投げます。 パースエラーの箇所を記録するために特製に Error データ型を 使います。例外は関数を呼ぶことで捕捉し、有益なエラーメッセージを 印字する処理をします。

example12.hs で使えるコード
-- これはパースエラーを表現する型です
data ParseError = Err {location::Int, reason::String}

-- これをエラークラスのインスタンスとします
instance Error ParseError where
  noMsg    = Err 0 "Parse Error"
  strMsg s = Err 0 s

-- モナド型構築子については、Either ParseError を使います
-- これは Left ParserError で失敗を表現し、
-- 型 a の成功した結果は Right a で表現します
type ParseMonad = Either ParseError

-- parseHexDigit は ParseMonad モナド内で、単一の16進数を 
-- Integer に変換し、個々の不正文字に対してエラーを投げます
parseHexDigit :: Char -> Int -> ParseMonad Integer
parseHexDigit c idx = if isHexDigit c then
                        return (toInteger (digitToInt c))
                      else
                        throwError (Err idx ("Invalid character '" ++ [c] ++ "'"))

-- parseHex 16進数の文字列をパースし ParseMonad モナド内で
-- Integer に変換します。parseHexDigit からのパースエラーは
-- parseHex からの例外復帰を引き起こします。
parseHex :: String -> ParseMonad Integer
parseHex s = parseHex' s 0 1
  where parseHex' []      val _   = return val
        parseHex' (c:cs)  val idx = do d <- parseHexDigit c idx
                                       parseHex' cs ((val * 16) + d) (idx + 1)

-- toString は Integer を ParseMonad モナド内で String に変換します
toString :: Integer -> ParseMonad String
toString n = return $ show n

-- convert は16進数文字列を10進文字列に変換します。
-- 入力文字列上でのパースエラーは出力文字列として
-- エラーの説明メッセージを生成します。
convert :: String -> String
convert s = let (Right str) = do {n <- parseHex s; toString n} `catchError` printError
            in str
  where printError e = return $ "At index " ++ (show (location e)) ++ ":" ++ (reason e)

Prev: Maybe モナド TOC: 目次 Next: List モナド