みどりねこ日記

よくわからないけど、頑張りますよ。

Haskellモナドトランスフォーマー(1)

ここまでで、モナドがどういうもので、IOモナドは不純な計算のため、Maybeは失敗するかもしれない計算のために、…というように、どのように使われるのかが理解できたのではないかと思います。

実際の作業の中で、複数のモナドの特性を扱いたい場合があります。もちろん、IO (Maybe a)みたいなことを書いて使うことも出来ますが、それでは値を取り出すとき、doブロックの中でパターンマッチングを行わなくてはなりません。モナドのちからを使えば、それすら回避することが出来ます。

ということで、これからしばらくモナドトランスフォーマーについて書いていきます。これは特別な型で、モナドに適用した時、新たな合成されたモナドを生成します。このモナドはそのどちらの性質も持ち合わせることになります。

IT業界に生きる上で、ユーザーにパスワードを強固なものにするようにすすめるプログラムという、よくある問題を例にして考えてみます。一般的な解決方法として、ユーザがパスワードを設定する時、そのパスワードが最低限の長さをもち、かつ1文字以上のアルファベット、1文字以上の数値、などといった制限をクリアしたもののみ通すというものがあります。
ユーザからパスワードを受け取る関数は以下のように書くことができるでしょう。

getPassword :: IO (Maybe String)
getPassword = do
	s <- getLine
	if isValid s then return $ Just s
	else return Nothing

getPasswordが実行された時、かならず同じ結果になると決まっているわけではないので、IOモナドになり、また、パスワードが制限に引っかかった場合Nothingが返ってくるようにしたいのでMaybeモナドになります。
isValidはわりとどうでもいいのですが、以下のようにしておきます。

isValid :: String -> Bool
isValid s = length s >= 8 && any isAlpha s && any isNumber s && any isPunctuation s

モナドトランスフォーマーを使う利点は、getPasswordを綺麗に書くというためではなく(もちろんなりますが)、むしろコードの利用を簡単にするために使われます。以下はモナドトランスフォーマーを利用しない場合の、getPasswordを使った関数です。

askPassword :: IO ()
askPassword = do
	putStrLn "Insert your new password: "
	maybe_value <- getPassword
	if isJust maybe_value
		then do
			putStrLn "Storing in database..."
			--...
	else ....

maybe_valueが必要になる上、パスワードが大丈夫なのかどうかなどを改めてチェックする必要があります。
モナドコンビネータを使えば、パターンマッチを使用することなく1度でパスワードを生成することができます。この例ではありがたみがいまいちわからないかもしれませんが、追々複雑な例を見ていきます。