fixIO
上のcatは、ループをするのにcat_mainという関数を用意しなくてはならず、まあ再帰のみでループを実現しようとすると、この手の問題は付き物だけど(特に気にもならない)、不動点コンビネータがあれば、回避できる。Haskellでは、不動点コンビネータのMonad版があり、fix::(a->a)->aなのに対して、fixM::(a -> m a) -> m aという型。mが恒等モナドならば、fixM=fixなので、これはfixの一般化になっている
IOモナドの場合は、fixIOが組み込み(IORef使って定義できるらしいけど)。fixMaybeは
fixMaybe f = let r = f (fromJust r) in r
とか、そんな定義。上のcatをfixIOで書き直すと
import System.IO import Monad cat fname = (openFile fname ReadMode) >>=(\hdl -> (join (ap (fixIO (\f -> (return (\h -> (hIsEOF h) >>=(\eof -> if eof then return () else (hGetLine h)>>=putStrLn>>(f h)))))) (return hdl)))>>(hClose hdl))
意味分かりません!Lisp並に深いネスト。
cat_main = (fixIO (\f -> (return (\h -> (hIsEOF h) >>=(\eof -> if eof then return () else (hGetLine h)>>=putStrLn>>(f h))))))
と置けば多分分かりやすい。いや、分かりにくいよ!cat_main::IO (Handle -> IO ())。Handle -> IO ()でないのは、fixIOの返り値が、IO xxでないといけないので、それに合わせるため(ここでかなりハマった)。apは、"m (a -> b) -> m a -> m b"という型の関数。一生役に立たないと思うけど、monadic fixpointに慣れた。