最近の更新 (Recent Changes)

2014-01-01
2013-01-04
2012-12-22
2012-12-15
2012-12-09

Wikiガイド(Guide)

サイドバー (Side Bar)

--
← 前のページに戻る

5.3.3 新しいクロージャの作成

5.3.3.1 コンパイル時のクロージャの生成

Closure Basicで、クロージャを定義するには、fun文を使います。

fun文を使った一般的な関数の定義は以下のようになります。


クロージャ名変数 = {fun(引数の変数, 引数の変数, ...) ローカル変数定義または実行文: return 返り値}

この構文を見てわかるのは、以下の要素で構成されていることです。

1) クロージャ名

2) 引数の変数

3) ローカル変数定義

4) 実行文

5) 返り値

それぞれについてコンパイラの役割を見ていきましょう。

1) クロージャ名

コンパイラはクロージャには、生成された順に「closure番号」という名前をつけて登録します。 番号は追い番で付けられます。

クロージャからクロージャが呼び出された場合、つまりfun()の中でfun()が呼び出された場合には その階層関係をコンパイラは記憶します。

クロージャ自身の実行時の実体には、階層関係は関係なく、すべてのクロージャは並列して存在します。 作成元のクロージャが終了しても、作成されたクロージャは存在し続けます。

そんため、クロージャ内で呼ばれたクロージャであっても、呼び出し元のクロージャの変数やグローバル変数などに代入して呼び出せば、そのクロージャの作成された環境でいつでも実行できるのです。

作成されたクロージャのスコープは、それを代入する変数のスコープに従います。そして、変数のスコープはコンパイラが制御します。

次のプログラムを見てください。


inc = 0
dec = 0

counter = { fun()
        c = 0

        inc = {fun () c = c + 1: print c: return c}
        dec = {fun () c = c - 1: print c: return c}

        print c
        return 0
}


print "call counter()"
call counter()

print "call inc()"
call inc()

print "call inc()"
call inc()

print "call inc()"
call inc()

print "call dec()"
call dec()

------実行結果--------

$ descartes ClosureBasic counter.cbs
Compiling...
Run
call counter()
0
call inc()
1
call inc()
2
call inc()
3
call dec()
2

inc, dec, counterはグローバル変数です。

まず、コンパイラにより、counterに代入されるクロージャには、次のような処理を行うコードが設定されます。 最初にローカル変数としてc変数が定義され0に初期化されます。 そして、そのcounterクロージャの中で、ローカル変数cをインクリメント(+1), デクリメント(-1)するクロージャを作成して、inc, dec変数に代入します。 最後に、現在のc変数の初期値をプリントして、counterクロージャの定義は終了します。

さて、実行時にこのcounter変数に入ったクロージャを"call counter()"で呼び出すと、コンパイル時に定義されたクロージャが実行され、ローカル変数cとグローバル変数inc, decに値が代入され、初期値0が設定されてクロージャの実行は終了します。

この状態で、inc変数に入ったクロージャを実行すると、counterクロージャのローカル変数cの値を+1します。 dec変数に入ったクロージャを実行すると、counterクロージャのローカル変数cの値を-1します。

このようにクロージャ内のローカル変数にアクセスすることができるのは、そのクロージャ内で作成されたクロージャなのです。

しかも、そのクロージャ内で作成されたクロージャをグローバル変数にいれれば、クロージャ外からも実行が可能になるのです。

2) 引数の変数

Closure Basicでは、引数の変数は次のように扱われます。

1. クロージャが呼ばれると、引数と同じ名前の変数は現在の値をスタック上に退避します。

2. 引数の変数名が既存のものがない場合には新しい変数がローカル変数として作成されます。

3. クロージャの呼び出し時に設定された引数の値が、引数に対応する変数に代入されます。

4. クロージャが終了するときには、スタックに退避された値が引数に対応する変数に復旧されます。

5. クロージャが終了するときには、上記の2で作成された変数に対しては何も行わず終了時の値が保持されます。

引数の変数の値が、関数呼び出し(クロージャ呼び出し)ごとに退避されることによって、再帰関数(リカーシブ)の処理を可能とします。

引数の変数については、スタック上に領域をとってそれを直接アクセスする方法も考えられるのですが、Closure Basicでは引数もその他のローカル変数も統一した方法でアクセスしたかったのでこのような方式となっています。

3) ローカル変数定義

コンパイラは、クロージャ(関数)内で新たに定義された変数をローカル変数として作成します。

そして、新しいローカル変数を変数リストに追加します。 ローカル変数は、所属するクロージャのコンパイル中およびクロージャから呼び出された子クロージャや孫クロージャがコンパイル中には変数リストに留まります。そのため、変数リストに留まっている限りは、プログラムからその変数が見えて使える、つまり、それが変数のスコープとなります。

ローカル変数にはクロージャを代入することが出来ます。 これにより、クロージャのスコープの制御を変数のスコープにしたがって行うことが出来ます。

ローカル変数に代入されたクロージャは、そのクロージャの外側からはアクセスできません。 しかし、ローカル変数の外側で生成された変数に代入されたクロージャは、外側のクロージャ内で呼び出して使うことができるようになるのです。

4) 実行文

コンパイラは、ローカル変数の定義と同様に、クロージャ(関数)内に記述された実行文をコンパイルし、クロージャ内のコード領域に作成します。

しかし、クロージャ内の実行文は、所属するクロージャのコンパイル中にしか見えません。

つまり、実行文については、ローカル変数とは異なり、クロージャの呼び出しによってのみ実行されることになります。

Closure Basicには、goto文のような不規則にコード途中を呼び出す機能はないため、クロージャ単位で呼び出して実行文の処理を実行することになります。

5) 返り値

コンパイラは、クロージャ(関数)からの返り値を演算用のスタック上にプッシュするコードを作成します。

クロージャを呼び出した処理では、クロージャの処理が終わって返ってきたところで、スタック上から返り値を取り出すコードが置かれます。

5.3.3.2 コンパイル時のクロージャの後始末

1) クロージャ名

コンパイラは、クロージャ(関数)のコンパイルが終わった後にも他のクロージャからの呼び出しに備えてクロージャ名を覚えておきます。

2) 引数の変数

コンパイラは、クロージャの終了時には引数に設定されていた変数の値をスタックから取り出し、 クロージャの呼び出し前の値に戻すためのコードを設定します。

この処理により、再帰(リカーシブ)な関数呼び出しの場合に対しても、正しく引数を処理していくことが出来ます。

3) ローカル変数定義

コンパイラは、クロージャ(関数)のコンパイルが終わるとそのクロージャ内で使われていたローカル変数の情報を消去します。

クロージャであるため、情報は消去されてもローカル変数の領域と値はそのままで影響を受けないことに注意してください。

4) 実行文

コンパイラは、クロージャ(関数)のコンパイルが終わると対応するコードをクロージャ内に格納した状態のままにします。

5) 返り値

コンパイラは、返り値をスタックに設定するコードを生成します。