Haskell入門ハンズオン! #3: 当日用資料 4/5
はじめに
Haskell入門ハンズオン! #3
- 当日用資料 1/5
- 当日用資料 2/5
- 当日用資料 3/5
- (あとで上記3つをリンクにする)
とみてきた。パーサコンビネータとしての道具はそろった。これで四則演算の式を解析できる。
演算子のパース
はじめに演算子をパースする。演算子をあらわす型シノニムを定義する。
type Op = Integer -> Integer -> Integer
ここでの「演算子」は、「ふたつの整数を引数として、整数をかえす関数」だ。それぞれの演算子用のパーサを定義する。
ad, sb, ml, dv :: Parse Op
ad = char '+' `build` const (+)
sb = char '-' `build` const (-)
ml = char '*' `build` const (*)
dv = char '/' `build` const div
それぞれの記号をパースして、それぞれの演算を結果とするパーサだ。四則演算の演算子は、これら4つの演算子の「どれか」なので
op :: Parse Op
op = ad `alt` sb `alt` ml `alt` dv
対話環境で試してみる。
*Main> :reload
*Main> fromJust (parse op "+") 9 4
13
*Main> fromJust (parse op "*") 9 4
36
関数parseによって得られる関はJust値なので、関数fromJustでなかみの「演算子」を取り出している。取り出された「演算子」(これは関数なので)を数値9, 4に適用した。
式と項
「式」は、ふたつの「項」を演算子でつないだものだ。「項」は、「数」または、「式」を丸括弧でかこったものだ。このように、たがいにたがいの定義に使われているとき「相互再帰の関係にある」という。たとえば「式」の例として、つぎのようなものがある。
13+5, (12*3)+17, (30-5)/(20*4), ((41/2)+3)*5
「項」の例は、つぎのようになる。
19, (25+4), ((11*3)/(25-3)), (((315*8)+31)*7)
式のパース
式のパーサを定義する。
expr :: Parse Integer
expr = (term >@> op >@> term) `build` \((x, o), y) -> x `o` y
パーサtermは、あとで定義する。((x, o), y) -> x o
yは関数リテラルだ。引数をパターン((x, o), y)にマッチさせる。ここで、変数xとyにはパーサtermの結果としての「項」の値がはいる。変数oにはパーサopの結果として、演算がはいる。全体の結果としての「x o
y」は演算の結果になる。
項のパース
項のパーサを定義する。
term :: Parse Integer
term = number `alt` (char '(' @> expr >@ char ')')
「項」の定義は、つぎのようになる。
「数」または、「式」を丸括弧でかこったもの
パーサtermの定義は、「項」の定義を「そのまま」書けばいい。
できあがり
対話環境で試してみる。
*Main> :reload
*Main> parse expr "3*(5-2)"
9
より使いやすいように関数calcを定義する。
calc :: String -> Maybe Integer
calc = parse expr
対話環境で試す。
*Main> :reload
*Main> calc "3*(5-2)"
9
*Main> calc "(43+24)/(21-16)"
13
ほかの例でも試してみよう。ただし、
- 空白をいれてはならない
- 丸括弧は省略できない(余計な丸括弧もいれられない)
というところに注意する。