apply の部分をもう少し追います。
apply は引数 args に引数 Func を適用して、その結果を返すという関数で、そのパターンは二つあります。
- (1)最初から環境に登録されている関数を引数 args に適用する apply。(primitiveBindings で一番最初に登録された scheme 自信が持っている基本的な関数ですね)
apply :: LispVal -> [LispVal] -> IOThrowsError LispVal apply (PrimitiveFunc func) args = trace ("apply (PrimitiveFunc <primitive>) args:"++ showValList args) (liftThrows $ func args) -- Either の Right、Left を取って返す。
makeFunc で作られた Func型(関数を作るためのデータ)が define によって環境に登録されています。
(add 10 20) と実行されると add に対応するFunc型を探してきます。
-- Lisp>>> (add 10 200) -- eval env (function:(Atom "add") args:[Number 10,Number 200])
先に引数の部分を評価します。 10 20 は評価しても同じですが、 (* 2 4) 20 だと先に(* 2 4)を評価しなければならないのです。
- (2)ユーザが環境に登録した関数(Func型)を引数 args に適用する apply が呼ばれます。
apply (Func params varargs body closure) args = -- apply (Func Func{params=["x","y"] vararg=Nothing body=[List [Atom "+",Atom "x",Atom "y"]] -- closure=<env>}) -- args:[Number 10,Number 200] trace ("apply (Func "++showVal2 (Func params varargs body closure)++") args:"++ showValList args) -- varargs()Nothingもなくて params(["x","y"]) の数と args([Number 10,Number 200])の数が -- 一致しないのはエラーです。 (if trace ("num params:" ++show (num params)++" num args:"++show (num args)++" varargs == Nothing:"++ show (varargs == Nothing)) (num params /= num args && varargs == Nothing) -- NumArgs エラー "Expected 2 args; found values 1 20 30" then throwError $ NumArgs (num params) args else (liftIO $ bindVars closure $ -- (1) params(["x","y"]) と args([Number 10,Number 200])を zip すると -- [("x",Number 10),("y",Number 20)] になります。 trace("zip:" ++ show (zip params args)) (zip params args)) >>= -- (2) bindVarArgs varargs -- zip された値に bindVars を適用して環境に登録します。 -- すると、 "x" を評価すると Number 10 が返り、 -- "y" を評価すると Number 20 が返るようになります。 bindVarArgs varargs >>= -- (3) 環境がととのったのでここで Func型の body の部分が評価されます。 -- (define (add x y) (+ x y)) の (+ x y)の部分ですね。 -- evalBody:[List [Atom "+",Atom "x",Atom "y"]] -- ここでまた eval が呼ばれます。 -- eval env (function:(Atom "+") args:[Atom "x",Atom "y"]) -- "add" を環境から検索すると Func型が帰ってきましたが今度は PrimitiveFunc。 -- func (PrimitiveFunc <primitive>)<- eval env (Atom "+") -- argVals ([Number 10,Number 200])<- mapM (eval env) [Atom "x",Atom "y"] -- apply (PrimitiveFunc <primitive>) args:[Number 10,Number 200] -- Number 210 evalBody) where remainingArgs = trace ("remainingArgs:"++ show args ++" -> "++show (drop (length params) args)) (drop (length params) args) num = toInteger . length evalBody env = trace("evalBody:" ++ show body)(liftM last $ mapM (eval env) body) bindVarArgs arg env = trace ("bindVarArgs arg env") (case arg of{ Just argName -> liftIO $ bindVars env [(argName, List $ remainingArgs)]; Nothing -> return env ;})
まとめ
- (define (add x y) (+ x y))
eval env (List (Atom "define" : List (Atom "add" : [Atom "x",Atom "y"] : [List [Atom "+",Atom "x",Atom "y"]]))
(1) Func型のデータを作ります。
Func{params=["x","y"] vararg=Nothing body=[List [Atom "+",Atom "x",Atom "y"]] closure=<env>}
(2) "add"をキーとしてFunc型のデータを保存します。
defineVar env var:"add" value:Func{ params=["x","y"] vararg=Nothing body=[List [Atom "+",Atom "x",Atom "y"]] closure=<env>}
- (add 10 20)
eval env (function:(Atom "add") args:[Number 10,Number 20])
(1) "add" に対応するFunc型を取得します。
Func{params=["x","y"] vararg=Nothing body=[List [Atom "+",Atom "x",Atom "y"]] closure=<env>}
(2) 引数の各要素を評価します。
argVals ([Number 10,Number 20])<- mapM (eval env) [Number 10,Number 20]
(3) 取得したFunc型をargs:[Number 10,Number 20]に適用します。
apply (Func Func{params=["x","y"] vararg=Nothing body=[List [Atom "+",Number 10,Number 20]] closure=<env>}) args:[Number 10,Number 20]
-
- Func の params(["x","y"]) と eval の args([Number 10,Number 200])を zip -- > [("x",Number 10),("y",Number 20)] を作ります。
- zip された値に bindVars を適用して環境に登録します。
bindVarArgs [("x",Number 10),("y",Number 20)] var "x" --- value (Number 10) var "y" --- value (Number 20)
-
- Func型の body の部分を評価します。
evalBody:[List [Atom "+",Atom "x",Atom "y"]] eval env (function:(Atom "+") args:[Atom "x",Atom "y"])
-
- a) "+" を環境から検索すると numericBinop (+) が帰ってきます。
func (PrimitiveFunc <primitive>) <- eval env (Atom "+")
--- argVals ([Number 10,Number 20])<- mapM (eval env) [Atom "x",Atom "y"]
-
- c) numericBinop (+) を[Number 10,Number 200]に適用するとNumber 20 が返ります。
-- apply (PrimitiveFunc <primitive>) args:[Number 10,Number 20]
- "define" : DottedList、"lambda" 等のときは makeVarargs によりFunc型が作られ、varargs が Nothing でなくなる。そのときの動作は?
- Func型が作られる都度 Env も保存される。レキシカルスコープに対応するためと思われる。クロージャ(wikipedia)