LoginSignup
0
0

More than 5 years have passed since last update.

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

Posted at

参考URL

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

ソース

プリミティブ

evalで加減乗除などの計算を行えるようにします。

まずListの先頭がAtomのときにマッチするパターンを
追加します。

eval (List (Atom func : args)) = apply func $ map eval args

apply関数は次のようになります。

apply :: String -> [LispVal] -> LispVal
apply func args = maybe (Bool False) ($ args) $ lookup func primitives

いろいろと初めてみる関数が出てきますが、
まずはprimitivesから見ていきます。

primitives :: [(String, [LispVal] -> LispVal)]
primitives = [("+", numericBinop (+)),
              ("-", numericBinop (-)),
              ("*", numericBinop (*)),
              ("/", numericBinop div),
              ("mod", numericBinop mod),
              ("quotient", numericBinop quot),
              ("remainder", numericBinop rem)]

ここで使われているnumericBinopは以下です。

numericBinop :: (Integer -> Integer -> Integer) -> [LispVal] -> LispVal
numericBinop op params = Number $ foldl1 op $ map unpackNum params

unpackNumは以下のようになります。

unpackNum :: LispVal -> Integer
unpackNum (Number n) = n
unpackNum (String n) = let parsed = reads n in 
                          if null parsed 
                            then 0
                            else fst $ parsed !! 0
unpackNum (List [n]) = unpackNum n
unpackNum _ = 0

これでこの項目で定義した関数はすべて出揃いました。
ひとつずつゆっくりと調べていきます。

まずunpackNumはLispValをIntegerに変換します。
Numberならその中身をStringならその中身の文字列を
reads関数に適用します。

ここでreads関数は文字列のうち数字に変換できる部分と
変換できない部分に分けて返します。

> reads "123abc" :: [(Int,String)]
[(123,"abc")]

ここではIntを指定していますがDouble型なども指定可能です。

> reads "123.456abc" :: [(Double,String)]
[(123.456,"abc")]

unpackNum関数はIntegerを返すので、reads関数は
Integer型に変換できるものとできないものにわけます。

その結果は要素がひとつのリストになっているので
最初の要素を取り出します。

また要素はタプルになっているので
タプルの最初のものを取り出します。

fst $ parsed !! 0

もし、Integerに変換できなかったり、Number,String以外の
LispValが渡された場合は0を返します。

unpackNum関数の役割がわかったので、
次にnumericBinop関数をみていきます。

numericBinop :: (Integer -> Integer -> Integer) -> [LispVal] -> LispVal
numericBinop op params = Number $ foldl1 op $ map unpackNum params

opはIntegerを2つとりIntegerを返す関数です。
これは+や-、*や/などにあたります。
paramsはLispValのリストです。

foldl1は第一引数の関数を第二引数のリストに順々に適用していきます。

> foldl1 (+) [1,2,3,4]
10
> foldl1 (*) [1,2,3,4]
24

LispValのリストparamsはunpackNumで
すべてIntegerに変換されて渡されます。

つまり、numericBinOp関数は演算子と値のリストが渡され、
その結果を返す関数になります。
結果はNumberコンストラクタでLispVal型にされます。

numericBinopを使っているprimitivesをみてみます。

primitives :: [(String, [LispVal] -> LispVal)]
primitives = [("+", numericBinop (+)),
              ("-", numericBinop (-)),
              ("*", numericBinop (*)),
              ("/", numericBinop div),
              ("mod", numericBinop mod),
              ("quotient", numericBinop quot),
              ("remainder", numericBinop rem)]

numericBinopには演算子しか渡されていないため、
後ほどLispValのリストを渡すようです。

また、各要素はStringとnumericBinOpのタプルになっています。

primitivesを使うapply関数をみていきます。

apply :: String -> [LispVal] -> LispVal
apply func args = maybe (Bool False) ($ args) $ lookup func primitives

まずlookup関数は最初の引数の文字列を持つタプルを、
第二引数のリストから探しだして、そのタプルの後ろの要素を返します.

言葉にするとややこしいので例をみてみます。

> lookup "+" [("+",1),("-",2)]
Just 1
> lookup "-" [("+",1),("-",2)]
Just 2
> lookup "/" [("+",1),("-",2)]
Nothing

結果はMaybe型になります。
apply関数で使われているmaybeは3つの引数をとり、
3番めの評価結果がNothingなら一番目の引数を返し、
評価結果がJustならその中身を2番めの引数に適用します。

これも説明するとわかりにくいので例を見てみます。

> maybe (0) (+100) $ lookup "+" [("+",1),("-",2)]
101
> maybe (0) (+100) $ lookup "-" [("+",1),("-",2)]
102
> maybe (0) (+100) $ lookup "/" [("+",1),("-",2)]
0

Justがの場合はその中身に(+100)が適用された結果が、
Nothingの場合は0が戻っています。

結果としてapply関数は渡された文字列に対応する関数があれば
その関数にLispValのリストを適用した結果を返し、
なければBool Falseを返します。

これで、やっとevalに戻ってこれました。

eval (List (Atom func : args)) = apply func $ map eval args

今まで見てきたものからこの関数は、
funcの文字列に対応する関数があれば
それにargsを評価した後のリストを、
無ければBool Falseを返す処理をしているということがわかります。

つまり(+ 1 1)のようなリストを評価すると2を(実際はNumber 2)、
(* 1 2 3 4)のようなリストを評価すると24(Number 24)
を返す関数になります。

これで四則演算などの計算が行えるようになりました。

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