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
構文を追加する。