【お知らせ】プログラミング記事の投稿はQiitaに移行しました。

クロージャ

今の仕組みのまま、特別な拡張なしにクロージャを実現できないか考えてみました。結論から言うと見送りですが、せっかく検討したので記録を残しておきます。

関数と構造体

関数のスタックフレームをローカル変数の構造体だと考えれば、関数と関数オブジェクトは同じになりそうです。

関数のスタック構造
引数
ret先
EBP(退避)
ローカル変数
ワークエリア

ローカル変数を構造体のメンバと見なすわけです。そうなると内部的に関数を構造体として処理できるので、構造体とブロックを統合できるかもしれません。

thiscall

この考え方で関数オブジェクトを作ると、ローカル変数を構造体として任意の場所に確保することになります。それがスタックであれば従来の関数と同じです。

ヒープならポインタを通知する必要があります。これはまるっきりthiscallそのものです。C++に例えると、普通の関数を関数オブジェクトに変換する場合に、ローカル変数をすべてメンバにしてしまうことに相当します。

// 関数
int Func()
{
	int a = 2, b = 3;
	return a + b;
}

// 関数オブジェクト
class
{
private:
	int a, b;
public:
	int operator()()
	{
		a = 2;
		b = 3;
		return a + b;
	}
} FuncObject; 

すべての関数を関数オブジェクトとして生成すれば、手動で関数オブジェクトを定義する手間は省けます。しかしC言語に渡せなくなります。

レキシカルスコープ

シンプルな仕組みのままどうにかならないかを考えてみました。C#クロージャを使うときに、呼び出し先にローカル変数を操作させることが多いです。

// C#でクロージャを使う例
int a = 1;
Func(() =>
{
    a++;
}); 

呼び出し先がクロージャを保持しなければ、レキシカル変数をスタックの外に出して保持する必要はありません。しかしクロージャの呼び出し時にレキシカル変数のアドレスを渡す必要性は変わりません。これではthiscallと同じです。

結論

関数ポインタとレキシカル変数のポインタを持った構造体を定義して、関数ポインタの代わりに受け渡せば事足ります。しかしそれをC言語にコールバックとして渡すことができません。

C言語との相互運用性の問題が解決するまではLLPMLにクロージャのサポートを盛り込むことは見送りました。ただし副産物として構造体の延長線上で関数を扱うというアイデアが得られたので、それを基にASTの継承関係を調整してみます。