トップ 最新の日記 ユーザー登録 ログイン ヘルプ

osdev-jの日記 RSSフィード

2006-07-302006年7の月もそろそろ終わりか

[]実装から見る、クロージャ 23:05 実装から見る、クロージャ - osdev-jの日記 を含むブックマーク

せっかくクロージャが話題にのぼったのでメモしてみようかと思い立つ。

さて、Schemeだとこんな風。

(define (add x) (lambda (y) (+ x y)))

(define a (add 2))
(define b (add 3))

(print "a = " (a 1))
(print "b = " (b 1))

どの辺りがクロージャか。lambdaで関数を返してるところか?

ならば、Cで書いたら?

#include <stdio.h>

typedef int (*type_fn)(int);

static int x;

int function(int y)
{
	return x + y;
}

type_fn add(int z)
{
	x = z;
	return function;
}

int main()
{
	type_fn a = add(2);
	type_fn b = add(3);
	printf("a = %d\n", a(1));
	printf("b = %d\n", b(1));
}

ところが、実行してみると結果が違う。

なぜか?

Schemeではlambdaによって関数が出来た時点の外側の環境(まさにその時点の参照してる変数)をクロージャに閉じ込める。

だから、define a した時点では x = 2 だし、define b した時点では x = 3 だ。

してみるに、クロージャってのはCでは「関数ポインタ+環境に付随する変数」に違いない。以下でどうか?

#include <stdio.h>

typedef struct type_env type_env;
typedef int (*type_fn)(type_env*, int);

struct type_env {
	type_fn f;
	int x;
};

int function(type_env* env, int y)
{
	return env->x + y;
}

type_env add(int z)
{
	// 本当はmalloc使ったほうが分かりやすいか?とも思ったけれど、長さの関係上割愛。
	type_env env = { function, z };
	return env;
}

int main()
{
	type_env a = add(2);
	type_env b = add(3);
	printf("a = %d\n", a.f(&a, 1));
	printf("b = %d\n", b.f(&b, 1));
}

今度はSchemeとCの実行結果が一致する。

ところで、最初のSchemeの例に立ち戻り、環境てのは字句構造から見た関数の外側のその時点の変数のことだった。

(print "a = " (a 1))

を実行した時点で a に束縛された関数から外側が見えて良い理由はレキシカルスコープだ。


さて、クロージャは「関数+環境(関数実行時に参照する変数)」だった。

そして、レキシカルスコープだからクロージャ大義名分が立つ。


てな訳で、、、C++ の例も書こうかと思ったのだけれど、長いからこの辺りで。

(例えば STL関数オブジェクトなんかは近い使い方。とだけ。)

トラックバック - http://osdevj.g.hatena.ne.jp/osdevj/20060730