Delphi : クロージャ(完成版)
クロージャというのは、関数内関数などで、関数が定義されたところの環境(ローカル変数やローカル関数)への参照を維持した関数オブジェクトを作成する機能のことです。
過去の記事
[id:lethevert:20060106:p2] - ファンクタの作成
[id:lethevert:20060107] - クロージャの原案
[id:lethevert:20060109:p2] - fix関数によるフィボナッチ関数の作成
クロージャの型について
クロージャの型について、どのような型が一番適切なのかをいろいろ考えていたのですが、結論が出ました。
原案では、「TObject -> TObject」型で実装し、後に「Pointer -> Pointer」に変更したのですが、結論として、「Pointer -> ()」型がもっとも汎用的だという結論になりました。Delphiの型システムとメモリ管理の特徴を考えた上での結論です。
結果を蓄積する場合には、返値を使わずに、フリー変数を利用すれば実現できます。
function Sum(N: Integer): Integer; var Accum: Integer; procedure SumAccum(I: Integer); begin Accum := Accum + I; end; begin DoRepeat(N, _(@SumAccum)); Result := Accum; end;
また、複数の引数を受け取ったり、返値を返したりする関数のクロージャを作りたい場合は、以下のようにレコードを作ります。
type PSS_S = ^TSS_S; TSS_S = record in0, in1, ret: string; end; function ConCat(lst: TStringDynArray): string; procedure ConCatAccum(data: PSS_S); begin data.ret := data.in0 + data.in1; end; var call: TSS_S; begin call.ret := ''; FoldL(_(@ConCatAccum), @call, lst); Result := call.ret; end;
これを使って、先日のフィボナッチ関数を作ったのがこちら([id:lethevert:20060110:p2])です。
クロージャの実装
Delphi : クロージャ : 補助関数
Apply???という補助関数を適宜作ることで、クロージャの呼び出しをラップすることができます。
例)
type PSS_S = ^TSS_S; TSS_S = record in0, in1, ret: string; end; function ApplySS_S(F: IClosure; in0, in1: string): string; var call: TSS_S; begin call.in0 := in0; call.in1 := in1; F._(@call); Result := call.ret; end;
Delphi : クロージャ : fix関数によるフィボナッチ関数の作成
以上、全てを使ってこういうことができますという例。
重要ポイントだけ抜き出し。
//固定点を求める関数 function fix(maker: IClosure): IClosure; procedure f(data: PI_I; _Maker: PClosure); var __f: IClosure; procedure _f(data: PI_I); begin ApplyC_C(_Maker._, __f)._(data); end; begin //返値としないので、通常のクロージャを選択 __f := _(@_f); _f(data); end; begin //返値とするので、環境永続化クロージャを選択 Result := _(@f, Init(maker), @Final); end; //フィボナッチ関数を作る関数 procedure fib_maker(data: PC_C); procedure fib_maker2(data: PI_I; F: PClosure); begin if data.in0 = 0 then data.ret := 0 else if data.in0 = 1 then data.ret := 1 else begin data.ret := ApplyI_I(F._, data.in0 -1) + ApplyI_I(F._, data.in0 -2); end; end; begin //返値とするので、環境永続化クロージャを選択 data.ret := _(@fib_maker2, Init(data.in0), @Final); end;
以下、全文掲載。
続きを読む