HaskellとParsecでLisp REPL その7(if構文)
今回の変更
条件分岐のための構文を追加。
(if a b c)という式は、まずaを評価し、もし真ならbを、偽ならcを評価してその結果が値となる。
bとcのどちらかひとつしか評価されないのが重要なので、関数ではなく構文として定義。
実装としてはevalにルールをひとつ追加しただけ。
評価器
evalへの変更:
eval (List [Atom "if", ifForm, thenForm, elseForm]) = case eval ifForm of Bool True -> eval thenForm Bool False -> eval elseForm
条件を評価してからパターンマッチで次に評価する式を決めている。
この評価ルールは関数適用のルールより先に書く必要がある。関数適用の評価ルールは「先頭が任意のAtom型の任意のリスト」に当てはまるので、もしifのルールがその後に来てしまうと絶対に使われなくなってしまう。
LispVal・パーサ・REPL・Main
全部変更なし。構文をevalをちょこっといじるだけで加えられるのはいい。
注意点
(if a b)や(if a b c d)のような、ifと一緒に含まれる要素の数が3以外の場合、上記の評価ルールに当てはまらない- 次の評価ルールに当てはまってしまい、
ifが関数として評価される
- 次の評価ルールに当てはまってしまい、
という問題がある。回避しようと思うなら、「要素数が3以外の時はエラー」と明示的に評価ルールを追加すればいい。
実行
>> (if #t 5 0) 5 >> (if #f 5 0) 0 >> (if (= 5 5) (+ 5 2) (* 5 2)) 7 // ネスト挙動も正常 >> (if (= 5 2) (+ 5 2) (* 5 2)) 10 >> (if (> 4 3) 1 2 3) 0 // 要素数が一致しない場合、`if`が未定義の関数として扱われて結果が0になる >> (if 1 10 0) repl: src/Evaluator.hs:(8,3)-(10,31): Non-exhaustive patterns in case
おっと、ふと試してみたらバグが見つかった。たしかにifFormがBool型じゃない場合を想定していなかった。
case ifBool of Bool True -> eval envRef thenForm _ -> eval envRef elseForm
に修正しておこう・・・
次回
次回は「ユーザによる変数定義」機能を実装する。