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

trace を入れて define、その定義された関数の適用される様子を追ってみます。

  • 表示するための定義
instance Show LispVal where show = showVal2

showVal2 :: LispVal -> String
showVal2 (String contents) = "String \"" ++ contents ++ "\""
showVal2 (Atom name)       = "Atom \""   ++ name ++ "\""
showVal2 (Number contents) = "Number " ++ show contents
showVal2 (Bool True)       = "Bool True"
showVal2 (Bool False)      = "Bool False"
showVal2 (List contents)   = "List " ++ showValList contents
showVal2 (DottedList head tail) = "DottedList " ++ showValList head ++ " " ++ showVal tail
showVal2 (PrimitiveFunc _) = "PrimitiveFunc <primitive>"
showVal2 (Func {params = args, vararg = varargs, body = body, closure = env}) = 
  "Func{params=" ++ show args  ++ " vararg=" ++ show varargs ++ " body=" ++ show body
  ++ " closure=<env>}"

showValList :: [LispVal] -> String
showValList xs  = "[" ++ showValList' xs ++ "]" 

showValList' :: [LispVal] -> String
showValList' []     = "" 
showValList' [x]    = showVal2 x 
showValList' (x:xs) = showVal2 x ++ "," ++ showValList' xs
  • define の様子を表示
eval env (List (Atom "define" : List (Atom var : params) : body)) =
    trace("eval env (List (Atom \"define\" : List (Atom "++show var++ " : "++ showValList params ++ 
          " : "  ++ showValList body++"))")
         (makeNormalFunc env params body >>= defineVar env var)
  • define で定義された関数を実行する様子を表示
eval env (List (function : args)) = do
    func    <- trace("eval env (function:("++showVal2 function++") args:"++ showValList args++")" )
                    (eval env function)
    argVals <- trace("-- func ("++showVal2 func++")<- eval env ("++showVal2 function++")")
                    (mapM (eval env) args)
    trace("-- argVals ("++ showValList argVals ++")<- mapM (eval env) "++ showValList args)
         (apply func argVals)
  • 関数を適用する様子

ここはもっと詳しく調べます。

apply :: LispVal -> [LispVal] -> IOThrowsError LispVal
apply (PrimitiveFunc func) args =
    trace ("apply (PrimitiveFunc <primitive>) args:"++ showValList args)
          (liftThrows $ func args)
apply (Func params varargs body closure) args = 
  trace ("apply (Func "++showVal2 (Func params varargs body closure)++") args:"++  showValList args)
    (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 $ 
                          -- zip:[("x",Number 10),("y",Number 20)]
                      trace("zip:" ++ show (zip params args))
                           (zip params args)) >>= 
                      bindVarArgs varargs >>= evalBody)
    where remainingArgs = drop (length params) args
          num = toInteger . length
          evalBody env = liftM last $ mapM (eval env) body 
          bindVarArgs arg env = case arg of
              Just argName -> liftIO $ bindVars env [(argName, List $ remainingArgs)]
              Nothing -> return env 

それでは実行してみます。

  • (define (add x y) (+ x y))

まず、(define (add x y) (+ x y))が評価されると "define" のひとつにマッチして、そこに定義されている関数が実行されます。

$ rlwrap runghc listing9.hs
Lisp>>> (define (add x y) (+  x y))
eval env (List (Atom "define" : List (Atom "add" : [Atom "x",Atom "y"] : [List [Atom "+",Atom "x",Atom "y"]]))

-- (define name value)のタイプ
-- value を評価した結果が Env に var をキーとして保存される。
eval env (List [Atom "define", Atom var, form]) =
    eval env form >>= defineVar env var

-- (define (add x y) (+  x y)) のタイプ
-- 今回はこちら var:"add"  params:[Atom "x",Atom "y"] 
--              body:[Atom "+",Atom "x",Atom "y"]
eval env (List (Atom "define" : List (Atom var : params) : body)) =
    makeNormalFunc env params body >>= defineVar env var

makeNormalFunc が呼ばれ env params body の引数によりFunc型のデータが作られます。
Func型にはその都度今まで定義された環境(Env)も含まれています。

makeNormalFunc Func{params=["x","y"] vararg=Nothing body=[List [Atom "+",Atom "x",Atom "y"]] closure=<env>}

defineVar が呼ばれ、定義された関数名とそれを実行するための関数を作るのに必要なFunc型のデータが環境(Env)に保存されます。
Func型にはその都度今まで定義された環境も含まれています。

defineVar env 
          var:"add" 
          value:Func{ params=["x","y"]
                      vararg=Nothing 
                      body=[List [Atom "+",Atom "x",Atom "y"]] 
                      closure=<env>}

Func{params=["x","y"] vararg=Nothing body=[List [Atom "+",Atom "x",Atom "y"]] closure=<env>}
  • (add 10 20)

eval env (function: args)のパターンにマッチ。

-- eval env (function:(Atom "add") args:[Number 10,Number 20])
eval env (List (function : args)) = do
    func    <- eval env function    -- "add" に対応する Func をEnvより取得
    argVals <- mapM (eval env) args -- argsの要素を評価。
                                    -- (今回はこれ以上評価できない)
    --  func を argVals に適用する。
    apply func argVals
Lisp>>> (add 10 20)
eval env (function:(Atom "add") args:[Number 10,Number 20])

-- func (Func{params=["x","y"]
              vararg=Nothing
              body=[List [Atom "+",Number 10,Number 20]]
              closure=<env>})
              <- eval env (Atom "add")

-- argVals ([Number 10,Number 20])<- mapM (eval env) [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

次に apply 関数が呼ばれ、Func型から作られる関数を引数に適用します。

apply (Func Func{params=["x","y"] vararg=Nothing 
                 body=[List [Atom "+",Atom "x",Atom "y"]] closure=<env>}) 
      args:[Number 10,Number 20]

引数部分が評価されます。
eval env (Atom "+")で "+" は本物の加算をする関数に。
[Atom "x",Atom "y"]を評価すると[Number 10,Number 20]になって、"+"から作られた関数が適用されて Number 30 になります。

eval env (function:(Atom "+") args:[Atom "x",Atom "y"])
-- func (PrimitiveFunc <primitive>)<- eval env (Atom "+")
-- argVals ([Number 10,Number 20]) <- mapM (eval env) [Atom "x",Atom "y"]
apply (PrimitiveFunc <primitive>) args:[Number 10,Number 20]
Number 30

もっと詳しく追います。
この説明ではきっと初めての人には意味不明だ・・・。