Parsec 入門


Parsec で Applicative を使う

Applicative

Text.Parsec では Applicative が使える。例えば数字の文字列をスキャンして Int 型のデータを得るには次のようにする。
Prelude Text.Parsec> parseTest ((read :: String -> Int) <$> many
 digit) "123"
123

また、両端のカッコを取り除いて間の文字列を取り出すには次のようにする。

Prelude Text.Parsec> parseTest (char '(' *> many letter <* char
 ')') "(hello)"
"hello"

モナドから取り出さずにモナド値を処理できる Applicative は、モナドを扱うプログラムでは絶大な効果を発揮するようだ。四則演算のパースをするプログラムを Applicative で書き直したら計算までしてくれた。

Prelude> :l calc.hs
[1 of 1] Compiling Calc ( calc.hs, interpreted )
Ok, modules loaded: Calc.
*Calc> parseTest expr "(1+2)*(3+4)"
21

Applicative の使用例

ファイル名:calc.hs

module Calc where

import Text.Parsec

expr :: Parsec String () Int expr = sum <$> ((:) <$> term <*> many (char '+' *> term))

term :: Parsec String () Int term = product <$> ((:) <$> factor <*> many (char '*' *> factor))

factor :: Parsec String () Int factor = (char '(' *> expr <* char ')') <|> ((read :: String -> Int) <$> (many1 digit))

上のプログラムは PEG で記述した下記の文法(Wikipedia より)をほぼ逐語的に書き下したものになっている。パーサを実装するときの Parsec と Applicative の威力が分かる。

Value ← [0-9]+ / '(' Expr ')'
Product ← Value (('*' / '/') Value)*
Sum ← Product (('+' / '-') Product)*
Expr ← Sum

パーサを設計する難しさは、プログラムの実装よりも、文法をどう設計して記述するかの難しさによっているような気がする。PEG で記述された文法は比較的容易に Parsec で実装できるのではないか。