「Write Yourself a Scheme in 48 Hours」 を写経してみる(14) : Scheme関数の定義(3)

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 "+")
    • b) [Atom "x",Atom "y"]の各要素を評価すると[Number 10,Number 20]が返ります。
--- 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)