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])です。
クロージャの実装
unit OptClosure; interface type TClosureFunction = procedure (data: Pointer); TClosureMethod = procedure (data: Pointer) of object; TClosureFunctionWithEnv = function (data, env: Pointer): Pointer; IClosure = interface procedure _(data: Pointer); end; function _(aFunction: Pointer): IClosure; overload; register; function _(aFunction: TClosureFunction): IClosure; overload; register; function _(aFunction: TClosureMethod): IClosure; overload; register; function _(aFunction, aEnv, aFinal: Pointer): IClosure; overload; register; implementation // ============================================================================= // 通常関数と関数内関数向け // ============================================================================= type TClosureF = class(TInterfacedObject, IClosure) private fFunction: TClosureFunction; fContext: Pointer; procedure _(data: Pointer); end; function mkClosureF(aCode: Pointer): IClosure; register; function make(aCode, aContext: Pointer): IClosure; register; var Impl: TClosureF; begin Impl := TClosureF.Create; with Impl do begin fFunction := TClosureFunction(aCode); fContext := aContext; end; Result := Impl; end; asm // begin push ebx push esi push ebp mov esi,edx mov ebx,eax // Result := make(aCode, EBP) mov ecx,esi pop edx mov eax,ebx call make // end pop esi pop ebx end; function _(aFunction: Pointer): IClosure; asm jmp mkClosureF end; function _(aFunction: TClosureFunction): IClosure; asm jmp mkClosureF end; procedure TClosureF._(data: Pointer); var aContext: Pointer; begin aContext := fContext; asm push aContext end; fFunction(data); asm pop aContext end; end; // ============================================================================= // Pointer -> () 型メソッド向け // ============================================================================= type TClosurePM = class(TInterfacedObject, IClosure) private fMethod: TClosureMethod; procedure _(data: Pointer); end; function _(aFunction: TClosureMethod): IClosure; var Impl: TClosurePM; begin Impl := TClosurePM.Create; with Impl do begin fMethod := aFunction; end; Result := Impl; end; procedure TClosurePM._(data: Pointer); begin fMethod(data); end; // ============================================================================= // 関数(環境付き) // ============================================================================= type TClosureFE = class(TInterfacedObject, IClosure) public destructor Destroy; override; private fFunction: TClosureFunctionWithEnv; fEnv: Pointer; fFinal: procedure(Env: Pointer); procedure _(data: Pointer); end; function _(aFunction, aEnv, aFinal: Pointer): IClosure; var Impl: TClosureFE; begin Impl := TClosureFE.Create; with Impl do begin fFunction := aFunction; fEnv := aEnv; fFinal := aFinal; end; Result := Impl; end; procedure TClosureFE._(data: Pointer); begin fFunction(data, fEnv); end; destructor TClosureFE.Destroy; begin fFinal(fEnv); Inherited; end; end.