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
に修正しておこう・・・
次回
次回は「ユーザによる変数定義」機能を実装する。