Arantium Maestum

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

HaskellとParsecでLisp REPL その6(数値比較と論理演算)

今回の変更

github.com

前回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ではない・・・

LispValHaskellIntegerBoolに変換する関数:

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