read' :: (Read a) => String -> IO (Maybe a)

数日前(http://d.hatena.ne.jp/sshi/20060630/p2)のread'の続き。IO (Maybe a)を返すread'を作ってみた。エラーを吐く代わりにNothingを返す、がIOモナドの中だけでしか使えないので注意。
readが吐くエラーを補足できないよー、あきらめたよー。といっていたらb2oxさんが丁寧なコメントをつけてくれた。コメントを熟読した結果、僕のなかでは、

  • IOモナドで発生した例外はtry,catchで補足できる
  • String -> IO a というIOモナドの内部用(?)のread(readIO)がある
  • readで例外が発生するのは入力された文字列を解析するときだろうからIOモナドに組みこむのが自然

という理解がなされたので、それに従ってread'を作りなおした。型はString -> Maybe a からString -> IO (Maybe a)に変更されているので注意。

read' :: (Read a) => String -> IO (Maybe a)
read' s = catch (readIO s >>= (return . Just)) (const $ return Nothing)

catchがミソ。catchはなにも起こらなければ第一引数に与えたIOモナドをそのまま返す。IOモナドの中で例外が発生した場合だけ、発生した例外を第二引数に渡して実行する。今回は成功したらJustで値をくるみ、例外が発生したら問答無用でNothing。問答無用さ加減を表現するためにconstを使ってみた。ちなみに、GHCだとなにもimportしなくても動作する。
書いてみたら自明なコードにしかならなかった。readIOしてcatch!readIOしてcatch!。これくらいで書けるんだったら、わざわざ汎用な関数定義なんてしないで、その時々に適切な例外処理をreadIO使ってちゃんと書くのが正しいんだろうな。今頃気がついてしまった。とほほ。