HaskellとParsecでLisp REPL その6(数値比較と論理演算)
今回の変更
前回Bool値を定義できたので、=、/=、<、>などやand/orを実装する。
Functions
Functionsモジュールのprimitivesにどんどん追加していく:
primitives :: [(String, [LispVal] -> LispVal)] primitives = [ ... ("=", numBoolBinOp (==)), ("/=", numBoolBinOp (/=)), (">", numBoolBinOp (>)), (">=", numBoolBinOp (>=)), ("<", numBoolBinOp (<)), ("<=", numBoolBinOp (<=)), ("and", boolBoolBinOp (&&)), ("or", boolBoolBinOp (||)) ]
やはりHaskellの関数をwrapする形で[LispVal] -> LispVal型の関数を作成していく。
まず、数値二つをとってブール値を返すnumBoolBinOp:
numBoolBinOp :: (Integer -> Integer -> Bool) -> [LispVal] -> LispVal numBoolBinOp op [x, y] = Bool $ op (lvToInteger x) (lvToInteger y) numBoolBinOp op _ = Bool False
パターンマッチを使って、引数の数が2ならopを数値化した引数に適用してBool化、2以外ならBool Falseを返す。以前numerifyという名前にしていた関数をlvToIntegerに変えてみた。
次に、ブール値を一つ以上とってブール値を返すboolBoolBinOp:
boolBoolBinOp op args = Bool $ foldl1 op $ map lvToBool args
おっとboolBoolBinOpの型定義し忘れてる。型推論があるので記事を書くまで気付かなかった・・・
正しくは:
boolBoolBinOp :: (Bool -> Bool -> Bool) -> [LispVal] -> LispVal boolBoolBinOp op args = Bool $ foldl1 op $ map lvToBool args
これはnumBinOpと似たような定義。opの引数と戻り値の型がBoolで一致しているのでfoldl1が使える。やはりbinOpではない・・・
LispValをHaskellのIntegerやBoolに変換する関数:
lvToInteger :: LispVal -> Integer lvToInteger (Number n) = n lvToInteger _ = 0 lvToBool :: LispVal -> Bool lvToBool (Bool b) = b lvToBool _ = False
現在はまだLispValの型がNumber型やBool型でない場合は0やFalseにしている。
Functions以外のコード
変更なし
実行
>> (> 5 4) #t >> (= 6 (* 3 2)) #t // 四則演算をネストできる >> (= 4 4 4) #f // 引数が2以外の場合は必ず#f >> (and (> 6 3) (< 6 5)) #f // 論理演算も可能 >> (or (> 6 3) (< 6 5) (<= 10 7)) #t // 論理演算は引数2以外でも可 >> quit
次回
if構文を追加する。