はじめてのモナド

まず課題1。

亀プログラム処理系プログラムがエラーメッセージを返せないのは、すべてのエラーがMaybeモナドのNothingになってしまい、それ以外の情報を抽出できないため。

一方で、Maybeモナドを亀プログラム処理系プログラムで使用するのは自然。なぜならモナドは、「成功するかどうかわからない処理を連続して行い、失敗した時点でやめる」ためのものだから。これはパース処理にうってつけの動作。

そこで、エラー時のメッセージを保持するdata構造を定義し、Maybeと同様に動作できるようにする。そのためには、MaybeモナドのNothingがエラーメッセージ文字列を保持するようなdata構造であればよい。あまり深く考えず、Maybeモナドを拡張したモナドPMaybeを定義する。

data PMaybe a	= PJust a | PErr String deriving Show

instance Monad PMaybe where
  PJust x >>= f = f x
  PErr s >>= f = PErr s
  return x = PJust x
  fail s = PErr s

違いはfailだけなので、モナド則は満たす。

これを使ってパーサを定義する。簡単のため、「go」しかコマンドのないプログラムの簡易パーサを定義してみる。

{-
	パーサ型クラス
 -}
class TTParser a where
  ttParse :: a -> PMaybe a



{-
	コンテキストデータ構造
	後ろのwordのリストをパースして
	前のStringリストに結果を入れていく
 -}
newtype TCntxt = TCntxt ([String], [String])

instance Show TCntxt where
  show (TCntxt a) = show a



{-
	TCntxtはTTParserのインスタンス
 -}
instance TTParser TCntxt where
  ttParse (TCntxt (xs, ("go":ys)))
    = PJust (TCntxt ( ("pGo":xs), ys ))
  ttParse (TCntxt (xs, []))
    = PJust (TCntxt (xs, []))
  ttParse (TCntxt (xs, (y:ys)))
    = PErr ("***ERROR*** [" ++ y ++ "]")



{-
	テストデータ
 -}
testJustCntxt = TCntxt ([], ["go","go"])
testErrCntxt = TCntxt ([], ["go","og"])

実行すると以下のようになる。

*Main> ttParse testJustCntxt
PJust (["pGo"],["go"])
*Main> ttParse testJustCntxt >>= ttParse
PJust (["pGo","pGo"],[])
*Main> ttParse testJustCntxt >>= ttParse >>= ttParse
PJust (["pGo","pGo"],[])

*Main> ttParse testErrCntxt
PJust (["pGo"],["og"])
*Main> ttParse testErrCntxt >>= ttParse
PErr "***ERROR*** [og]"

注みっつ。

  • dataではなくnewtypeを使ったのは、newtypeを使ってみたかっただけ。それ以上の意味なし。
  • newtypeのデータコンストラクタの引数はひとつだけ。
  • TTParser型クラスもこの時点では必要ないが、今後の発展を考えて使ってみたかっただけ。