Nice example of anon methods for functional programming. I created these methods as part of something I'm working on, and I thought I'd post here for the person arguing in another post that he couldn't really see the value in them. Personally, I think this is really cool!

Nice example of anon methods for functional programming. I created these methods as part of something I'm working on, and I thought I'd post here for the person arguing in another post that he couldn't really see the value in them. Personally, I think this is really cool!

You don't need to know the details of this first function, just see how the others use it. Note that TProc is pre-defined in one of Delphi's units. Other than that, nothing else needs to be defined to make this code work. It's all right here. (I mean, aside from the things that are in the class...)

//========================================================
procedure TAPU.DoForAllTracks( aProc : TProc );
var
att : TAudioTrackType; // TAudioTrackType is an enum
chan : TPlayerChannel;
// AudioTrack is a var in the class defined as:
// AudioTrack : array [TAudioTrackType] of TPlayerChannel;
begin
for tt := Low(TAudioTrackType) to High(TAudioTrackType) do
begin
chan := AudioTrack[att];
if Assigned(chan) then
aProc(chan);
end;
end;
//========================================================
procedure TAPU.FlushQs();
begin
DoForAllTracks(
procedure (aChan : TPlayerChannel)
begin
aChan.ClearQueue();
end
);
end;
//========================================================
procedure TAPU.PlayAllChannels();
begin
DoForAllTracks(
procedure (aChan : TPlayerChannel)
begin
Play( aChan );
end
);
end;

Comments

  1. David Schwartz I'm an old school developper, I like syntax sugar but I know the cost of things.

    Let's take this tiny code
    {code}
    type
    TTestOne = reference to procedure;
    TTestTwo = procedure;

    procedure TestOne(proc: TTestOne);
    begin
    proc();
    end;

    procedure TestTwo(proc: TTestTwo);
    begin
    proc();
    end;

    procedure TestTwoCallBack;
    begin
    ShowMessage('Test two');
    end;

    procedure TForm1.Button1Click(Sender: TObject);
    begin
    TestOne(procedure
    begin
    ShowMessage('Test one');
    end
    );
    end;

    procedure TForm1.Button2Click(Sender: TObject);
    begin
    TestTwo(TestTwoCallBack);
    end;
    {code}

    look like the same...

    now let's see the generated code


    {code}
    type
    TTestOne = reference to procedure;
    TTestTwo = procedure;

    procedure TestOne(proc: TTestOne);
    begin
    proc();
    end;

    procedure TestTwo(proc: TTestTwo);
    begin
    proc();
    end;

    procedure TestTwoCallBack;
    begin
    ShowMessage('Test two');
    end;

    procedure TForm1.Button1Click(Sender: TObject);
    {
    Unit1.pas.48: begin
    005CE57C 55 push ebp
    005CE57D 8BEC mov ebp,esp
    005CE57F 83C4F0 add esp, - $10
    005CE582 33C9 xor ecx,ecx
    005CE584 894DF4 mov [ebp-$0c],ecx
    005CE587 8955F0 mov [ebp-$10],edx
    005CE58A 8945FC mov [ebp-$04],eax
    005CE58D 33C0 xor eax,eax
    005CE58F 55 push ebp
    005CE590 68E1E55C00 push $005ce5e1
    005CE595 64FF30 push dword ptr fs:[eax]
    005CE598 648920 mov fs:[eax],esp
    005CE59B B201 mov dl,$01
    005CE59D A194E45C00 mov eax,[$005ce494]
    005CE5A2 E8B998E3FF call TObject.Create
    005CE5A7 8945F8 mov [ebp-$08],eax
    005CE5AA 8D45F4 lea eax,[ebp-$0c]
    005CE5AD 8B55F8 mov edx,[ebp-$08]
    005CE5B0 85D2 test edx,edx
    005CE5B2 7403 jz $005ce5b7
    005CE5B4 83EAF8 sub edx, - $08
    005CE5B7 E870F2E3FF call @IntfCopy
    Unit1.pas.49: TestOne(procedure
    005CE5BC 8B45F8 mov eax,[ebp-$08]
    005CE5BF 85C0 test eax,eax
    005CE5C1 7403 jz $005ce5c6
    005CE5C3 83E8F4 sub eax, - $0c
    005CE5C6 E8A1FDFFFF call TestOne
    Unit1.pas.54: end;
    005CE5CB 33C0 xor eax,eax
    005CE5CD 5A pop edx
    005CE5CE 59 pop ecx
    005CE5CF 59 pop ecx
    005CE5D0 648910 mov fs:[eax],edx
    005CE5D3 68E8E55C00 push $005ce5e8
    005CE5D8 8D45F4 lea eax,[ebp-$0c]
    005CE5DB E834F2E3FF call @IntfClear
    005CE5E0 C3 ret
    }
    begin
    TestOne(procedure
    begin
    ShowMessage('Test one');
    end
    );
    end;

    procedure TForm1.Button2Click(Sender: TObject);
    {
    Unit1.pas.98: begin
    005CE5EC 55 push ebp
    005CE5ED 8BEC mov ebp,esp
    005CE5EF 83C4F8 add esp, - $08
    005CE5F2 8955F8 mov [ebp-$08],edx
    005CE5F5 8945FC mov [ebp-$04],eax
    Unit1.pas.99: TestTwo(TestTwoCallBack);
    005CE5F8 B8C4E35C00 mov eax,$005ce3c4
    005CE5FD E8B2FDFFFF call TestTwo
    Unit1.pas.100: end;
    005CE602 59 pop ecx
    005CE603 59 pop ecx
    005CE604 5D pop ebp
    005CE605 C3 ret
    }
    begin
    TestTwo(TestTwoCallBack);
    end;
    {code}

    ReplyDelete
  2. Paul TOTH when you take up the fight for quad-core 2.6 GHz CPUs wasting 95% of their bandwidth updating widgets in hidden browser windows, let me know. Until then, this is just another argument about how many angels can dance on the head of a pin. (You might also compare it to the overhead involved in using basic Interfaces; that's FAR worse of a CPU resource hog!)

    ReplyDelete
  3. FWIW this is another half baked implementation in the Delphi compiler - technically an anonymous method is not the same as a closure.

    An anonymous method as its name says is a method without a name. A closure is a function with an enclosing scope. Unfortunately in Delphi all anonymous methods are implemented as closures with their overhead even if not needed because there is nothing to be captured. To implement the capturing they are being implemented as special interfaces.

    If there is no capturing to be done there is no technical reason not to allow this:

    var
    p: procedure(t: TTrack);
    begin
    p := procedure(t: TTrack) begin ShowMessage(t.Title);
    p(someTrack);
    end:

    The method does not capture anything, everything it works with is the passed parameter. So the compiler could easily take the anonymous method and implement it as standalone routine which would be assignable to p. Some compilers are even able to completely inline such code (oh, hi C++)

    Many usecases of anonymous methods actually don't need to capture variables (uses as callbacks, filters or transforms) and sometimes they even capture something implicitly and accidentally without the developer even noticing.

    So the compiler could do two things:

    - allow anonymous method (I mean
    inline code, not a variable of type TProc or something) being assignable to a plain function pointer if it does not have any state (no capturing).

    - if assigning to a reference of (like TProc) it should check as well if there needs to be any capturing, if not it can craft a static interface method table for the anonymous method that does not require objects in the back (just like done manually in System.Generics.Defaults).

    That should be achievable "easy enough" because the compiler already knows about all the captures to handle as it puts them as fields into the object created for the closure.

    ReplyDelete

Post a Comment