参考URL
#ソース
https://github.com/0zawa/scheme_in_48_hours/blob/master/evaluation_2.hs
条件分岐
if文を評価できるようにします。
eval (List [Atom "if", pred, conseq, alt]) =
do result <- eval pred
case result of
Bool False -> eval alt
otherwise -> eval conseq
やっていることは単純で、Listの先頭がAtomの"If"なら
リストの2つめのpredを評価して、
その結果がBool Falseならリストの4番めを評価、
それ以外ならリストの3番めを評価、
としているだけです。
例として、
(if (> 2 3) "no" "yes")
上記のようなリストの場合、predが(>2 3)
predがBool Falseのとき評価されるのが"yes"
predがそれ以外のとき評価されるのが"no"
になります。
car
carはSchemeでリストの先頭を返す関数です。
Haskellではheadに相当します。
car :: [LispVal] -> ThrowsError LispVal
car [List (x : xs)] = return x
car [DottedList (x : xs) _] = return x
car [badArg] = throwError $ TypeMismatch "pair" badArg
car badArgList = throwError $ NumArgs 1 badArgList
List,DottedListなら先頭の要素を返し、
それ以外ならエラーにしています。
cdr
cdrは「クダー」と読むようです。
Schemeでリストの先頭以外の要素を返す関数です。
Haskellではtailになります。
cdr :: [LispVal] -> ThrowsError LispVal
cdr [List (x : xs)] = return $ List xs
cdr [DottedList [xs] x] = return x
cdr [DottedList (_ : xs) x] = return $ DottedList xs x
cdr [badArg] = throwError $ TypeMismatch "pair" badArg
cdr badArgList = throwError $ NumArgs 1 badArgList
Listの場合は単純に先頭以外の[LispVal]を
ListコンストラクタでLispValにしています。
DottedListは
DottedList [LispVal] LispVal
の[LispVal]が要素1つのリストならLispValを返し、
[LispVal]が要素2つ以上のリストならそのリストの
先頭以外の部分とLispValをDottedListコンストラクタで再構築して
LispValにします。
それ以外のケースは全てエラーにします。
cons
consは「コンス」と読みます。
Haskellでは「:」にあたります。
この実装は少しずつ、順を追ってみていきます。
まず、任意のLispValと、空リストを持つListをconsすると、
List [x1]になります。
cons :: [LispVal] -> ThrowsError LispVal
cons [x1, List []] = return $ List [x1]
次に、任意のLispValと中身のあるListをconsすると
LispValとListの中身を連結して、ListコンストラクタでLispValにします。
cons [x, List xs] = return $ List $ x : xs
任意のLispValとDottedListにconsを適用した場合は
LispValとDottedListの先頭を連結して、
DottedListコンストラクタでLispValにします。
cons [x, DottedList xs xlast] = return $ DottedList (x : xs) xlast
上記以外のパターンで、LispVal2つのリストが来た場合は
DottedListコンストラクタでLispValにします。
cons [x1, x2] = return $ DottedList [x1] x2
ここに来るのはx2がListでもDottedListでも無い場合全てなので、
x1がListやDottedListの場合も含まれます。
それ以外のケースはエラーにします。
cons badArgList = throwError $ NumArgs 2 badArgList
#eqv
Listの2つの要素が等しいかを判定するeqv関数を定義します。
eqv :: [LispVal] -> ThrowsError LispVal
eqv [(Bool arg1), (Bool arg2)] = return $ Bool $ arg1 == arg2
eqv [(Number arg1), (Number arg2)] = return $ Bool $ arg1 == arg2
eqv [(String arg1), (String arg2)] = return $ Bool $ arg1 == arg2
eqv [(Atom arg1), (Atom arg2)] = return $ Bool $ arg1 == arg2
eqv [(DottedList xs x), (DottedList ys y)] = eqv [List $ xs ++ [x], List $ ys ++ [y]]
eqv [(List arg1), (List arg2)] = return $ Bool $ (length arg1 == length arg2) &&
(all eqvPair $ zip arg1 arg2)
where eqvPair (x1, x2) = case eqv [x1, x2] of
Left err -> False
Right (Bool val) -> val
eqv [_, _] = return $ Bool False
eqv badArgList = throwError $ NumArgs 2 badArgList
かなり長いですが、Bool,Number,String,Atomの比較は単純です。
それぞれの中身を==で比較しているだけです。
DottedListの場合はListに変換してからeqvを再帰させて
判定します。
Listの場合は複雑なので、少しずつみていきます。
まずlengthでリストの要素数を比較します。
リストの要素数が等しければ、
all eqvPair $ zip arg1 arg2
を実行します。
all関数は、リストを取る関数とリストが与えられて、
全てのリストの要素が、関数でTrueを返すかどうかを判定します。
言葉にすると少しわかりにくいので例を示します。
> all even [2,4,6,8]
True
> all even [2,4,6,7]
False
> all even [1,4,6,8]
False
evenは偶数かどうかを判定する関数です。
eqv関数内でallに与えられるリストは
zip arg1 arg2
です。
これはarg1とarg2のリストの各要素をタプルにしたリストを返します。
> zip [1,2,3] [4,5,6]
[(1,4),(2,5),(3,6)]
eqvPair関数は
where eqvPair (x1, x2) = case eqv [x1, x2] of
Left err -> False
Right (Bool val) -> val
と定義されている通り、各タプルの要素をeqvで評価し、
その結果を返します。
eqv関数はそれ以外の場合はエラーになります。