もなもなErrorモナド

モナドの短めのサンプルコードを自力で書いてみる「もなもな」シリーズ第二回目、今回はErrorモナド(http://www.sampou.org/haskell/a-a-monads/html/errormonad.html)。
Errorモナドは、いわゆる例外を扱うモナドで、Exceptionモナドともいうらしい。実態はMaybeモナドとほとんど同じ。Maybeモナドは、何かの値に加えて、どの値でもない「Nothing」を持つモナドだったけど、Errorモナドは本来期待される値と、例外発生時の値を持つモナド。例えば、「本来は数値計算をするが、内部で例外が発生したときは文字列で原因を通知する」といった関数に使えて、この関数の型は、具体的なErrorモナドの型構築子であるEitherを使うと、"Either String Int"と書ける。値としては、正常の場合が「Right 値」、例外の場合が「Left 値」になる。

サンプルコードの説明

今回は割り算をする関数mydivを定義した。0で割ろうとすると例外発生。この関数をdo記法(つまり結合される一連のモナド)の中で使うと、前から処理が進んでいって、例外が発生すると全体がその例外の値になる。その結果を判定する関数を付けてやれば、例外をキャッチできる。do_mydivがdo記法の中で例外を発生させたりさせなかったりするサンプル、do_mydiv_cは例外をcatchして例外メッセージをすりかえるサンプル。list_mydivはdo記法使わないバージョン。
しかし、全く無意味なつまんないサンプルだな。

サンプルコード

import Control.Monad.Error -- Hugsではエラーがでる

mydiv :: (Fractional a) => a -> a -> (Either String a)
mydiv x n = (if n == 0 then throwError "divide by zero" else return (x/n))

do_mydiv n = do a <- mydiv 10 n
                b <- mydiv 10 (n-2)
                c <- mydiv 10 (n*2)
                return (a,b,c)

list_mydiv list = (sequence ( map (mydiv 10) list))

list_mydiv_c list = (sequence ( map (mydiv 10) list))
                    `catchError` (\e-> throwError $ (show list)++" include zero")

main =do print (do_mydiv 5)
         print (do_mydiv 0)
         print (do_mydiv 2)
         print "---"
         print (list_mydiv [1,2,3,4,5])
         print (list_mydiv [0,2,3,4,5])
         print (list_mydiv_c [0,2,3,4,5])
         print (list_mydiv [1,2,3,0,5])
         print (list_mydiv_c [1,2,3,0,5])
         print (list_mydiv [1,2])

{-
-- main実行時の出力

Right (2.0,3.3333333333333335,1.0)
Left "divide by zero"
Left "divide by zero"
"---"
Right [10.0,5.0,3.3333333333333335,2.5,2.0]
Left "divide by zero"
Left "[0.0,2.0,3.0,4.0,5.0] include zero"
Left "divide by zero"
Left "[1.0,2.0,3.0,0.0,5.0] include zero"
Right [10.0,5.0]

-}

注意

実は標準のHaskell98言語環境では、Eitherはモナドではないようだ。Eitherをモナドとして扱ったり、catchErrorやthrowErrorを使うには、import Control.Monad.Errorっていうおまじないが必要。で、この中にHaskell98にはない言語拡張が利用されているらしい。GHCだと動作する。というわけで、Hugsでは、importをけずって以下のコードを追加すればサンプルコードが動作する。

--Hugsでこのサンプルコードを動かすための最低限の記述
--多分、モナド的な筋は良くない
instance Monad (Either e) where
    return a = Right a
    (Left a) >>= _ = (Left a)
    (Right a) >>= f = f a

throwError = Left
catchError (Left e)  handler = handler e
catchError m  _       = m

さらに

型クラスErrorとMonadErrorのインスタンスを定義すると、Either相当のものを自由に定義できる、らしいが、そこまでは立ち入らないことにする。簡単に使うにはEitherで十分な気がするし。