アキュムレータ・ジェネレータ
Dで書かれていないのが寂しいので、書いてみました。本当は寂しくもなんともなくてただ暇なだけです。
まずは、素直(?)に実装します。
int delegate(int) gene(int x){ return (int y){ return x += y; }; } void main(){ auto f = gene(1); writefln(f(2)); writefln(f(3)); writefln(f(4)); writefln(f(5)); }
実行結果。
3 6 10 15
一見うまくいっているように見えますが、他の関数を挟んだとき、悲劇は起こります。
void main(){ auto f = gene(1); writefln(f(2)); // 3 writefln("hoge"); // hoge writefln(f(3)); // 4272259 writefln(f(4)); // 4272263 writefln(f(5)); // 4272268 }
な、なんだってー!?
結局のところ、お亡くなりになったスタックフレームの変数を参照しているのが誤りなわけですが。
関数抜け出してもお亡くなりにならない物が欲しいわけです。とりあえず静的変数を使ってみましょう。
静的変数はコンパイル時定数で初期化されなければいけないので、何か変に冗長なコードになります。
int delegate(int) gene(int i){ static int x = 0; x = i; return (int y){ return x+=y; }; }
これでもまだ足りなくて、このままでは作られるアキュムレータ全てが同じ静的変数を共有してしまいます。
結局本当に必要なのは「関数呼び出しの度に作られる関数を抜け出してもお亡くなりにならない物」なわけですが…
ここで少し妥協して、ジェネレータに渡せる値はコンパイル時定数でならない、とするともう少し理想形に近づきます。
int delegate(int) gene(int i)(){ static int x = i; return (int y){ return x+=y; }; }
これで関数呼び出しの度に(正確には関数テンプレートの実体化の度に)新しい静的変数が作られます。
かなりいい感じになりましたが、実はこれでもダメで、同じ値から作られたアキュムレータが同じ静的変数を共有してしまいます。
つまり、Dで完璧なアキュムレータ・ジェネレータは書けません!Dは劣った言語です!!!Lisp神!!!!!!111
…まあ勿論、普通にやれば書けます。
関数オブジェクト使うとか、ちょっと泥臭いですがデリゲートの.ptrプロパティでスタックフレームのポインタ弄れるのでそれ使うとか。こんな風に。
ただそれだと負けた気がする、ということで他の方法を思案していたのですが、無理そうです。
結局の所、デリゲート自身に何らかの値を持たせる必要があって、そうなるとDでは値パラメタを受け取る関数テンプレートのデリゲートくらいしか残されていない…