IO+Maybe=?

Haskellについては、まだまだわからないことがいっぱいあるのですが、少しコードが書けるようになってきたころに躓いていたことがあります。Maybeモナドのことです。どこで知ったかは忘れてしまいましたが、Maybeは処理の中断をうまく表現できるので、C言語なんかでよくあるエラー処理をうまく隠すことができる...というのがあって、そりゃあ:

	:
if (!FDoThis(xxx, yyy, zzz))
	return;
if (!DoThat(yyy, zzz, ooo))
	return;
	
	:

というのが、

	doThis xxx yyy zzz
	dThat yyy zzz ooo

みたいになったら、さっぱりするなぁ、これは使わない手はないなぁ...と思っていたわけです。

Maybeモナドの定義を見ると、

instance Monad (Maybe a) where
	(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
	(>>=) Nothing _ = Nothing
	(>>=) (Just a) f = f a
		
	return = Just

みたいな感じで、なるほど上流からNothingが渡ってくると下流の処理は実行されなさそうです。しめしめなわけです。機会があったら使ってみようと思っていたところにやってきたのはこんな状況でした。

	doThis :: IO a
	doThat :: b -> IO ()
	
	foo :: a -> Maybe b
	
	main :: IO()
	main = do
		a <- doThis
		foo a >>= doThat

doThisの結果をfooに渡して、fooがJust aを返したら、そのaをdoThatに渡したい。もしfooがNothingを返したら、そのままdoThatを呼ばずに帰りたい…というよりも、Maybeが処理を中断してくれるのがわかっていたから、わざとfooがMaybeを返すようにした…

でも、このコードはコンパイルしてくれません。この例で使っているバインド(>>=)はいったい何の型なのだろうと考えました。使っているモナドはIOとMaybeなので、そのどちらか、あるいはその両者を組み合わせたものになる可能性があります…でも、doThatはIOでもfooはIOモナドな関数ではない...だからコンパイルはこけるようです…

それじゃあということで、

return (foo a) >>= doThat

にしたらどうなるでしょう?バインドの左辺はIO Maybe aになりますね...右辺はIO()でバインド自身はIOモナドのものになってしまいます...これではMaybeの特性である実行中断も使えません… おまけに、これではMaybe aがdoThatにわたってしまいます。

約束と違うじゃないか!と思ったりしたものです...色々なものを読んで、liftMとかliftIOとかやってみたのですが、Maybeのバインドを利用したコードをこのサンプルに適用することは結局できませんでした…

悩んでいたところに発見したのはモナドトランスフォーマーの話でした...あー、そうだよ、MaybeとIOを合成したモナドを作ればいいんじゃん...でもMaybeのモナドトランスフォーマーであるべきMaybeTは見当たりません。IOのモナドトランスフォーマーであるMonadIOもMaybeは受け入れてくれないようです。

どうやらモナドというのは事情が少し複雑で、モナドを2つ(以上)組み合わせたものが必ずしもMonadになってくれるわけではないようです。これがApplicativeだとApplicative同士を合成したものは必ずApplicativeになるそうですが...

MaybeとIOをつなげる手立てがライブラリの中で定義されていないのは、それができないからか、使えないからか...よくわかりませんが、自分でやってみても、IO Maybeという新しいモナドインスタンス宣言をコンパイルすることができません…

instance Monad  (IO Maybe) where
	(>>=) m f = 
		m >>= \mb ->
		dof mb f
		where
			dof Nothing = return Nothing
			dof (Just a) f = f a

	return a = return (Just a)

みたいなことをやってみるのですが、これはコンパイルしません…クラスインスタンスすら自分で書いたことないので、はっきり言って、自分でも何やってるか良くわかってません…
コンパイルしたとしても、モナド側を満足してるのかを調べる必要がありますね...ぱっと見では、IO Maybeに限ってみれば、モナド則はうまくいきそうにも見えますが…

(return x) >>= f == f x 
m >>= return == m 
(m >>= f) >>= g == m >>= (\x -> f x >>= g)

なんとなく、ここで頓挫といった感じです。モナドトランスフォーマーなしでモナドを重ねることはできるんでしょうか…

気を取り直して、どうやったらMaybeをIOな処理の流れの中でうまく使えるでしょうか?

一つには、maybe:: b -> (a -> b) -> Maybe a -> bというのがあります。これを使うとすれば、

	doThis :: IO a
	doThat :: b -> IO ()
	
	foo :: a -> Maybe b
	
	main :: IO()
	main = doThis >>= maybe (return ()) (doThat)

見たいな感じになりますね…それなりにコンパクトにまとまってくれるのですが、エラー処理がコンビネータに隠れて見えなくなるというかというとそうではないですね…もちろん、ほかのモナドが混入していない、純粋にMaybeな処理の流れだったら、>>=で処理の流れを中断できるんですけどね…

もし仮にIO Maybeのバインドを定義できたとしても、実は問題がもう一つあります。それは、IO Maybe型のdo文の各項は必ずIO Maybe aを返さなくてはならないということです...まぁ、たいした問題ではないのかもしれませんが...

ではでは...