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;

以下、全文掲載。

続きを読む