Scalaでパーサーを作ってみる〜10:レキシカルスコープとクロージャ

Scalaの勉強をはじめたので、とりあえず簡単なパーサーを作ってみてます。
http://d.hatena.ne.jp/nowokay/20111109#1320815540


前回は関数が導入されて、とうとう汎用処理がかけるプログラム言語になりました。
http://d.hatena.ne.jp/nowokay/20111114#1321244195


ここで前回の状態で次のようなコードを実行してみます。

val a = 3;
def f(x) ={
    a * x
};
println(f(5));
def g() = {
    val a = 10;
    println(f(5));
};
g();

関数fを5に適用という処理を、関数gの中と外で書いています。
この結果は次のようになります。

15
50

同じ引数での関数呼び出しが別の結果になっています。
関数fでは変数aという外部環境の変数を使っています。ただ、今回は関数呼び出しのときに外部環境をとりこんで処理を行っていたので、呼び出した時点での変数が関数処理に使われるということになります。
そのため、最初のf(5)ではval a=3の値が使われ、関数gの中のf(5)では関数gの中で定義したval a=10の値が使われるということになっています。
こういう変数スコープを動的スコープといいますが、関数の中で使っている変数について気にしておく必要があるのでプログラムは書きにくくなります。


ということで、外部環境の取り込みを、関数呼び出しのときではなく、関数定義のときに行うようにします。


そのためには関数と環境をまとめておく必要があるので、まず、Closureというケースクラスを作ります。

case class Closure(env: Environment, func: Func)

いえす!クロージャー!


そして、関数定義のときに、その時点での環境と関数をClosureでまとめます。

case FuncDef(name, func) =>{
    env.set(name, Closure(env, func))
}

この、環境と関数をまとめるということこそが、クロージャーのクロージャーたるゆえんですね。


あとは、関数呼び出しのときに、Closureで保持している環境を使って関数処理を行います。

case FuncCall(func, params) =>{
    visit(func) match{
      case f:Closure => {
          val local = new Environment(Some(f.env))
          (f.func.params zip params).foreach(case(pn, a) =>
            local.set(pn, visit(a))
          }
          eval(local, f.func.proc)
      }
    }
}


今回、構文の変更はありません。


最初のコードを実行してみます。

val a = 3;
def f(x) ={
    a * x
};
println(f(5));
def g() = {
    val a = 10;
    println(f(5));
};
g();


そうすると、次のようになります。

15
15

呼び出し時点の環境に左右されず、関数定義時点でどの変数が使われるかが確定するようになりました。
そうすると、実行しなくても静的に使う変数が確定できるので、これを静的スコープといいます。構文上から判断できるということで、構文スコープ、つまりレキシカルスコープともいいます。
レキシカルスコープといったほうが難しいことをしてるっぽいので、今回はこっちをタイトルにしました。


あしたは関数リテラル


ソースはここです。(コメントで指摘いただいた部分を修正済み)
https://gist.github.com/1345875/09a27775116cdb8d5d1d46cc669af46e3de417b6