人気ブログランキング | 話題のタグを見る

Text.Parsec.Prim: parserReturn

Text.Parsec.Prim の解読を続ける。string などの Parsec のパーサは a -> ParsecT s u m a 型の関数だ。つまり、引数を一つ取り、ParsecT s u m a 型のモナド値を返すモナド関数だ。

Prelude Text.Parsec> :t string
string :: Stream s m Char => String -> ParsecT s u m String

すなわち、パーサは Parsec モナドとして扱うことができるということだ。IO モナドに代表されるようにモナドは return 関数と bind 演算子 >>= を使うことができるのが特徴だ。ParsecT モナドも同様に return と >>= が使える。

Prelude Text.Parsec> parseTest (return "hello") "world"
"hello"

Prelude Text.Parsec> parseTest (string "hello" >>= \x -> return (x ++ x)) "hello, world"
"hellohello"

したがって, return 関数と >>= 演算子が Text.Parsec.Prim でどのように定義されているかを理解すればパーサの動作を理解することができる。今回はそのうちの return 関数について調べてみる。

return 関数の定義については、Text.Parsec.Prim では return 関数を直接に定義せず、parserReturn 関数を用いて定義している。したがって、parserReturn 関数の定義が return 関数の実際の定義になる。parserRetrun 関数の定義は次のようになる。

parserReturn :: a -> ParsecT s u m a
parserReturn x
__ = ParsecT $ \s _ _ eok _ ->
____ eok x s (unknownError s)

あっけないほど簡単だ。ParsecT データコンストラクタの (unParser) フィールドの中に次の関数を収めるだけだ。

\s _ _ eok _ -> eok x s (unknownError s)

ところで、ParsecT データ型を簡単に示すと次のようになる。

ParsecT {unParser = \s cok cerr eok eerr -> anyfunction}

ParsecT データ型は唯一つのの名前付きフィールド unParser を持ち、そのフィールドの中には、s cok cerr eok eerr の5つの引数を持つ無名関数が収められている。これらの引数の s はパーサ状態 State で、cok はマッチが成功し入力から文字列が消費されたときの値をモナドにラッピングする関数(関数が変数に束縛される)、cerr は消費が起きるがエラーの場合の処理を行う関数、eok はマッチが成功するが入力の消費が起きない場合を処理する関数、eerr は消費が起きないがエラーになる場合の処理を行う関数がそれぞれ束縛される。

parserReturn パーサのフィールドの関数に戻る。関数を再掲すると次のようになる。

\s _ _ eok _ -> eok x s (unknownError s)

これは、eok 関数を用いて parserRetun x 関数の引数の x とパーサ状態の s それにエラーメッセージの (unknownError s) を戻り値として返している。eok 関数は parseTest (内部的にはrunParsecT) で return 関数に与えられる。parserReturn では、この eok を用いてこれらの情報を m(Empty(m (OK a s err)) 型のモナド m に変換して戻り値としている。m は ParsecT の内部モナド、Empty や OK は単なるデータコンストラクタだ。

これは、関数本体のほうで cok, cerr, eok, eerr のいずれかを用いて戻り値を整形することができることを示している。ParsecT のフィールドの中身は単なる無名関数だが、unParser アクセサで取り出すことができて、s, cok, cerr, eok, eerr の実引数を与えることができる。

ところで、m(Empty(m (OK a s err)) 型の値は、unParser フィールドの無名関数に eok 関数が実引数として渡されたときにのみ戻り値として返される。無名関数が unParser アクセサで取り出されない場合は parserReturn は単に ParsedT s u m a 型のデータを渡すだけだ。したがって、parserReturn 関数の型は parserReturn :: String -> ParsecT s u m a となる。

話がややこしくなったが、もう一度整理してみる parserRetrun 関数に "foo" を与えると、unParser フィールドに無名関数が収められた次のような ParsecT s u m a 型のデータが戻り地として戻される。

ParsecT { unParser = \s _ _ eok _ -> eok "foo" s (unknownError s)}

このモナド値を parseTest 関数に与えるとunParser アクセサで取り出された関数に状態 s と関数 eok が渡され、最終的な parseTest (return "foo") "bar" 関数の戻り値である "foo" が戻される。

Prelude Text.Parsec> parseTest (return "foo") "bar"
"foo"

大まかな流れとしては、(1)return "foo" によって、無名関数をフィールドに入れた ParsecT 型のモナド値が作成される。(2)それを parseTest 関数に渡すことによって、モナド値の中の無名関数が取り出される。(3)同時に parseTest 関数によってパーサ状態 s や無名関数の戻り値をラッピングする関数 eok が実引数として無名関数に渡され、最終的な値が取り出される。という手順になっている。ParsecTのフィールドの中身が、たった1個の無名関数であるということを理解するとパーサモナドの動作の理解ができるようになる。

フィールドのデータが関数になっているデータ型というのはどういう感じなのかが気になったので実験してみた。

Prelude Text.Parsec> data Foo a b = Foo {unPack :: a -> b}
Prelude Text.Parsec> foo = Foo (\x -> x*x)
Prelude Text.Parsec> unPack foo 2
4
Prelude Text.Parsec> bar = Foo ((+10) . (unPack foo))
Prelude Text.Parsec> bar 2
Prelude Text.Parsec> unPack bar 2
14

引数が関数の場合もやってみた。

Prelude Text.Parsec> data Bar a = Bar {unpack :: a->(a->a)->(a->a)->a}
Prelude Text.Parsec> baz = Bar (\x f g -> f x)
Prelude Text.Parsec> foobar = Bar (\x f g -> g x)
Prelude Text.Parsec> unpack baz 2 (+1) (*2)
3
Prelude Text.Parsec> unpack foobar 2 (+1) (*2)
4

なんか面白い。

複数のモナドを >>= 演算子で繋ぐ操作では、次々にモナド値が作り出されていく。最終的なモナド値のフィールドの無名関数に対し、parseTest 関数が、s, cok, cerr, eok, eerr の実引数をモナド値のフィールドの関数に渡し、最終的な値が得られることになる。次回は、モナドとモナドをつなぐ >>= 演算子の仕組みを調べてみる。

by tnomura9 | 2019-07-06 23:26 | Haskell | Comments(0)
<< Text.Parsec.Pri... Text.Parsec.Pri... >>