F#でインタープリタ : クロージャの実装

やっとクロージャが実装できました。

    def counter (c) {
      fun () { c = c + 1 }
    }
    c1 = counter(0)
    c2 = counter(0)
    c1()
    c1()
    c2()
    c1()

上のコードを実行すると3が返ってきます。


関数呼び出しを評価するコードを抜粋。ごちゃごちゃしているけど現時点では満足です。クロージャを表現するのに、スコープチェーンのようなデータ構造を入れないと無理かなーと思いつつ、永続データ型のMapでなんとかなったのがうれしい。

    | PrimaryExpr(expr, arguments) ->
      match arguments with
      | Some(arguments) -> (* 引数があったら関数呼び出し *)
        eval env expr (fun exprEnv expr ->
        eval exprEnv arguments (fun argsEnv arguments ->
          match expr, arguments with
          | Var(id, Fun(parameters, block, funEnv)), Args(args) -> (* パラメータ、ブロック、静的スコープを取り出す *)
            eval argsEnv parameters (fun paramsEnv -> function
              | Params(``params``) ->
                let rec loop paramsEnv list = 
                  match list with
                  | [] -> (* 関数ブロックの処理*)
                    (* 関数ブロックのための環境を作成 *)
                    let blockEnv = (Map.toList funEnv) @ (Map.toList paramsEnv) |> Map.ofList
                    (* 関数ブロックを評価*)
                    eval blockEnv block (fun resultEnv result -> 
                      (* 自由変数だけを含んだ環境を作成 *)
                      let freeEnv = resultEnv |> Map.filter (fun key _ -> funEnv |> Map.containsKey key)
                      (* クロージャとして使えるように関数が保持する環境を更新 *)
                      let f = Fun(parameters, block, (Map.toList funEnv) @ (Map.toList freeEnv) |> Map.ofList)
                      (* 関数の束縛だけを変更した新しい環境を作成 *)
                      let newEnv = Map.add id f exprEnv
                      (* 関数の呼び出し終了。関数の束縛だけを変更した環境で後続処理。 *)
                      cont newEnv result)
                  | (param, arg) :: xs -> (* パラメータに引数を割り当て *)
                    assign paramsEnv param arg (fun paramsEnv _ -> loop paramsEnv xs)
                (* パラメータと引数の組のリストを再帰で処理 *)
                loop paramsEnv (List.zip ``params`` args)
              | _ -> failwith (sprintf "parameters %A is not found" parameters))
          | _ -> failwith (sprintf "function %A is not found" expr)))
      | _ -> (* 引数がなかったら式の評価 *)
        eval env expr cont

今回は素のMapでがんばりましたが、環境に相当する型を作ればもっとすっきり書けるはず。