LoginSignup
0
0

More than 5 years have passed since last update.

48時間でSchemeを書こう/評価: 第二部2

Posted at

参考URL

48時間でSchemeを書こう/評価: 第二部

ソース

条件分岐

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関数はそれ以外の場合はエラーになります。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0