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

# 参考URL
[48時間でSchemeを書こう/評価: 第一部](https://ja.wikibooks.org/wiki/48時間でSchemeを書こう/評価:_第一部)

#ソース
https://github.com/0zawa/scheme_in_48_hours/blob/master/evaluation_1.hs

# プリミティブ
evalで加減乗除などの計算を行えるようにします。

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

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

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

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

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

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

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

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

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


```hs
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関数は文字列のうち数字に変換できる部分と
変換できない部分に分けて返します。

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

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

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

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

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

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

```hs
fst $ parsed !! 0
```

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

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

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

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

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

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

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

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

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

```hs
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関数をみていきます。

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

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

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

```hs
> lookup "+" [("+",1),("-",2)]
Just 1
> lookup "-" [("+",1),("-",2)]
Just 2
> lookup "/" [("+",1),("-",2)]
Nothing
```
結果はMaybe型になります。
apply関数で使われているmaybeは３つの引数をとり、
３番めの評価結果がNothingなら一番目の引数を返し、
評価結果がJustならその中身を２番めの引数に適用します。

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

```hs
> 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に戻ってこれました。

```hs
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)
を返す関数になります。

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