もなもな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で十分な気がするし。