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

Text.Parsec.Prim: 関数のフィールドデータの扱い方

ParserT モナドの実体は、唯一のフィールドに無名関数を入れる代数的データ型だ。newtype 宣言で次のように定義されている。

newtype ParsecT s u m a
....= ParsecT {unParser :: forall b .
................ State s u
..............-> (a -> State s u -> ParseError -> m b) -- consumed ok
..............-> (ParseError -> m b).................. -- consumed err
..............-> (a -> State s u -> ParseError -> m b) -- empty ok
..............-> (ParseError -> m b).................. -- empty err
..............-> m b
............ }

このデータ構造には2つの特徴がある。一つはフィールドのデータが無名関数であることだが、もう一つは、引数の型が関数であるということだ。この意味がなかなか分かりづらかったので、同様のデータ型を作ってどういう動作をするのか試してみた。

まず、フィールドのデータが無名関数で、その引数が関数型であるような代数的データ型 Foo a を作ってみた。Foo はフィールド名 unpack のフィールドに無名関数を収める。無名関数の引数は第1引数が数値で第2引数と第3引数が関数だ。戻り値は数値になる。

Prelude> data Foo a = Foo {unpack :: a -> (a -> a) -> (a -> a) -> a}

そこで次のような Foo 型のデータ foo を作った。foo の unpack フィールドには \x f g -> if odd x then f x else g x という無名関数を入れた。この関数は x が odd なら f x を戻り値として返し、そうでない場合は g x を戻り値として返す。

Prelude> foo = Foo $ \x f g -> if odd x then f x else g x

これに実引数を渡して動作を確認した。

Prelude> unpack foo 3 (*10) (^2)
30
Prelude> unpack foo 4 (*10) (^2)
16

これを見ると Foo の unpack フィールドの関数が x の値で戻り値を切り替えるという抽象的な動作をする関数であることが分かる。この x の値次第で戻り値を切り替えるという性質を利用すると再帰関数も定義できる。これを検証するために、x の値が 0 のときと、それ以外の場合に振り分ける Foo 型のデータ baz を定義する。

Prelude> baz = Foo $ \x f g -> if x == 0 then f x else g x

この baz が x が 0 のときは f x を実行し、それ以外のときは g x を実行するという抽象的な性質に注目すると、次のように再帰関数も記述することができる。

Prelude> fact x = unpack baz x (\_ -> 1) (\s -> s * fact (s-1))
Prelude> fact 5
120

Foo のフィールドの関数は通常の関数だったが、モナド値を返すモナド関数にすることができる。そこで、Foo 型に似ているが Maybe a 型を返す関数を収めるデータ型 Bar を定義してみた。

Prelude> data Bar a = Bar {unpack :: a -> (a -> Maybe a) -> (a -> Maybe a) -> Maybe a}

foo と同じような Bar 型のデータ bar を次のように定義する。無名関数の本体のコードは foo の場合と全く同じだがこれは関数の抽象的な性質を考えると驚くべきことではない。

Prelude> bar = Bar $ \x f g -> if odd x then f x else g x

bar に実引数を与えた結果を次に示すが、関数の引数の部分のプログラムをモナドで記述することができる。

Prelude> unpack bar 3 (return) (\_ -> Nothing)
Just 3
Prelude> unpack bar 4 (return) (\_ -> Nothing)
Nothing

無名関数をフィールドデータとするデータ型や、引数が関数型の関数を自前で作って動作させてみたら、この形式のデータの扱いが少し分かってきた。Text.Parsec.Prim のパーサのプログラムの意味もなんとなく掴めた気がする。

by tnomura9 | 2019-07-17 21:38 | Haskell | Comments(0)
<< Text.Parsec.Pri... Text.Parsec.Pri... >>