Arantium Maestum

プログラミング、囲碁、読書の話題

HaskellとParsecでLisp REPL その7(if構文)

今回の変更

github.com

条件分岐のための構文を追加。

(if a b c)という式は、まずaを評価し、もし真ならbを、偽ならcを評価してその結果が値となる。

bcのどちらかひとつしか評価されないのが重要なので、関数ではなく構文として定義。

実装としては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

に修正しておこう・・・

次回

次回は「ユーザによる変数定義」機能を実装する。