Anonymous methods

Top  Previous  Next

Anonymous methods

Like property expressions and array operations, anonymous methods was introduced in Smart Pascal as a means to better compatibility with JavaScript. Both Delphi and Free Pascal support anonymous methods, but Smart Pascal has one advantage over these native compilers. Under native Pascal (compilers that produce executable machine code) you have class procedures and ordinary procedures. There is no difference between these two except that if you want to reference the first (class method) you must postfix the declaration with "of object". This way of referencing a class method is typically used for event declarations.

 

An event is declared as such in ordinary Object Pascal:

Type

  TMyEventType = procedure (sender:TObject;const value:Integer) of object;

 

 

If you omit the "of object" postfix, the reference can only be used on procedures and functions on unit level. 

The compiler will not allow you to reference an object method without "of object" being clearly defined.

 

Smart Pascal does not have this distinction. There is no "of object" in the Smart dialect. As such, anonymous methods can be applied on a wider scale than under native Object Pascal, including object based events and callback handlers.

 

Under Smart Pascal the example below is perfectly valid and compiles without any problems:

 

type

 TProcedureRef = procedure;

 

procedure w3_Callback(const aMethod: TProcedureRef; const aDelay: Float);

begin

   w3_SetTimeout(aMethod, aDelay);

end;

 

Code example: anonymous method version 1

procedure MyProc;

begin

 showmessage('You clicked the back button 2 seconds ago');

end;

 

procedure TForm1.W3Button20Click(Sender: TObject);

var proc : TProcedureRef;

begin

 

W3Button21.onClick := Procedure (sender:TObject)

         Begin

            w3_callback(MyProc, 2000);

         end;

end;

 

Code example: anonymous method version 2

procedure TForm1.W3Button20Click(Sender: TObject);

begin

W3Button21.onClick := Procedure (sender:TObject)

         Begin

            w3_callback(procedure()

                    begin

                       showmessage('You clicked the back button 2 seconds ago');

                    end2000);

         end;

end;

 

 

Events and event handlers: Since everything is an object in the JavaScript world, the "of object" ending for procedure references is not needed (and not supported). So where you under FreePascal or Delphi would write:

 

type

  TNotifyEvent = procedure (sender: TObject) of object;

 

 

The function pointers and closures are unified, you don’t have to distinguish between a procedure and a procedure  of  object, 

and you don’t have to distinguish a reference  to  procedure either. In other words, Pas2JS demands that you write:

 

type

  TNotifyEvent = procedure (sender: TObject);

 

 

as long as the parameters match (and result type for a function), the above type will accept standalone functions, object 

methods, interface methods, and now closures (and even record methods, which are just syntax sugar for standalone 

function with an implicit parameter). 

We will cover the anonymous method with examples. But for now, there are something you should learn before reading further.

There are 3 basic types of subroutines in Pas2JS. Each of the three types are available as both procedures or as functions. The first one is the traditional Pascal procedure or function. This is not technically a method, but can be used as a function pointer. The second type is a regular method. This is the type to which events are bound. The third type is anonymous methods.

Here is an example of the traditional Pascal procedure or function:

Code example: traditional Pascal procedure

type

  TWriter = procedure;

 

procedure CustomWriter;

begin

  writeln('Display this text');

end;

 

procedure TForm1.W3Button11Click(Sender: TObject);

var

  x: TWriter;

begin

  x := CustomWriter;

  x;  //Display this text

end;

 

and here is an example of a regular method...

 

Code example: regular method

type

  TWriter2 = procedure of object;

 

type

  TCustomClass = class(TObject)

  public

    procedure CustomWriter;

  end;

 

procedure TCustomClass.CustomWriter;

begin

  writeln('Display this text2');

end;

 

procedure TForm1.W3Button12Click(Sender: TObject);

var

  x: TWriter2;

  o : TCustomClass;

begin

  o := TCustomClass.Create;

 try

  x:= o.CustomWriter;  //Display this text2

  x;

 finally

  o.Free;

 end;

end;

 

Here is an example of an anonymous method.  Ignore the details for now and just pay attention to the declaration as it compares to the other two types.

 

Code example: anonymous method

type

  TWriter3 = reference to procedure;

 

procedure TForm1.W3Button13Click(Sender: TObject);

var

  x: TWriter3;

begin

  x := procedure

       begin

         writeln('Display this text3');

       end;

  x;

end;

 

 

Anonymous methods

 

An anonymous method is a method which is defined INLINE and doesn't have a name, are not called by name. So it must be used with a reference.  That reference can be a variable to which the method is assigned.  Here is an example:

 

 

Code example: Anonymous methods as variables

type

  TProc = reference to procedure;

 

procedure TForm1.W3Button14Click(Sender: TObject);

var

  myAnonymousMethod: TProc; { Declaration of an anonymous method reference }

begin

myAnonymousMethod :=   { Assignment of an anonymous method to a reference }

    procedure

    begin

      writeln('This was written from an anonymous method');

    end

myAnonymousMethod;  { Invocation of an anonymous method through its reference }

end;

 

 

Anonymous methods are not very different from a normal procedure. You'll notice that the procedure has no name. You'll also notice the missing semicolon after the final end. Without a name, we have to call them by reference. To do this, we need a reference to one: myAnonymousMethod: TProc;

 

That's easy.  But what's a TProc?  Just like a reference to a procedure or procedure of object, a method reference must have a type.  The type must be a matching procedure or function declaration. Instead of procedure or procedure of object, anonymous methods match signatures declared as reference to procedure.

 

You can now make further assignments by assigning the myAnonymousMethod to another reference.  This reference can be another variable, a method parameter, a class field, etc.  As long as they are all the same type, assignments can be made as needed.  In this assignment, the line ends with a semicolon.  This semicolon is denotes the end of the assignment line itself; it is not part of the anonymous method.

 

Because anonymous methods are so natively tied to a reference, it makes sense to use them mostly where passing references is easy to do.  Method parameters are class fields are good examples.  Let's expand our example to use the TAnonGetStrProc prototype and a simple logging example.

 

So far, our references have only been variables.  Modifying the variable example, we can pass a variable reference to an anonymous method as a parameter to another method.  This is far more useful than previous examples, as you'll see.

 

Code example: Anonymous methods as parameters

type

  TAnonGetStrProc = reference to procedure(value: string);

 

type

  TMyClass2 = class

  public

    procedure DoStuff(aLogMethod: TAnonGetStrProc);

  end;

 

procedure TMyClass2.DoStuff(aLogMethod: TAnonGetStrProc);

var

  i: integer;

begin

    // We don't have to define it in this class.

    aLogMethod('Error encountered, logged by anonymous method');

end;

 

procedure TForm1.W3Button15Click(Sender: TObject);

var

  LogMethod: TAnonGetStrProc;

  o: TMyClass2;

begin

  // Log method body is defined outside of any code that uses it.

  LogMethod :=

    procedure(value: string)

    begin

      Writeln(value);

    end;  // Semicolon is NOT part of anonymous method.

 

  // Here, we execute some code and PASS a reference to the log method.

    o := TMyClass2.Create;

  try

    o.DoStuff(LogMethod);

  finally

    o.Free

  end;

end;

 

 

In the previous example, the log methods (DoStuff) body is defined OUTSIDE the method that calls it. Instead of the reference being a local variable, it is a method parameter.  Okay, so what's so great about that?  We can define regular event methods that do that, right?  Remember that I said that anonymous methods are declared inline.  They don't have to live inside a class at all like a real method.  This means that we could change up the initialization section to get rid of the LogMethod variable altogether.

 

Code example: Anonymous methods Inline declaration

type

  TMyClass2 = class

  public

    procedure DoStuff(aLogMethod: TAnonGetStrProc);

  end;

 

procedure TMyClass2.DoStuff(aLogMethod: TAnonGetStrProc);

var

  i: integer;

begin

    // We don't have to define it in this class.

    aLogMethod('Error encountered, logged by anonymous method');

end;

 

procedure TForm1.W3Button16Click(Sender: TObject);

var o: TMyClass2;

begin

    o := TMyClass2.Create;

  try

    o.DoStuff(

              procedure(value: string)

              begin

                Writeln(value);

              end);

  finally

    o.Free

  end;

end;

 

 

See that the first parameter is not a variable, but the procedure body itself is stuffed in the call parameter list.  This is the cool thing about them.  They can be declared on the spot and passed around like any piece of data.  They are first-class citizens of the Smart Pascal language. The DoStuff procedure could even pass it to another method that it calls.

 

 

Instead of method parameters which must be passed around and can affect existing method signatures, refactoring may be done by storing anonymous methods in class fields.  Here is our previous example, converted.

 

Code example: storing anonymous methods in class fields

type

  TAnonGetStrProc2 = reference to procedure(value: string);

 

type

  TMyClass3 = class

  protected

    fLogMethod: TAnonGetStrProc2;

  public

    constructor Create(aLogMethod: TAnonGetStrProc2);

    procedure DoStuff;

  end;

 

  { TMyClass3 }

 

constructor TMyClass3.Create(aLogMethod: TAnonGetStrProc2);

begin

 fLogMethod := aLogMethod;

end;

 

procedure TMyClass3.DoStuff;

var

  i: integer;

begin

    fLogMethod('Error encountered, logged by anonymous method');

end;

 

procedure TForm1.W3Button17Click(Sender: TObject);

var o: TMyClass3;

begin

    o := TMyClass3.Create( procedure(value: string)

                            begin

                              Writeln(value);

                            end);

  try

    o.DoStuff;

  finally

    o.Free

  end;

end;

 

In the approach above, the DoStuff signature does not have to have a method parameter for the log method.  Instead, the method can use the fLogMethod field, which is initialized in the constructor.  No cleanup is needed in the destructor.  Now any methods that you add to the class can use the log method without a special parameter just for that.  Most importantly, existing methods don't have to have their signatures modified when adding this log method functionality to an existing class.

 

Note that the anonymous method itself is not followed by a semicolon. I've mentioned this before but it can take some reminding to get used it.  It can be confusing because you have seen me repeatedly end an anonymous method with a semicolon, but if you go back and look, the semicolon is there to terminate the assignment of an anonymous method to a variable.  It is NOT part of the anonymous method itself.

 

 

Anonymous Functions

 

You can use anonymous procedures and anonymous functions!  

Anonymous functions work identically, except that there must be a return type in the type definition and in the inline method 

body itself.

 

Code example: anonymous function

type

  TIntegerConvertFunc = reference to function(s: string): integer;  // Has a return type

 

procedure TForm1.W3Button18Click(Sender: TObject);

var

  myFunction: TIntegerConvertFunc;

 

begin

  myfunction :=

    function(s: string): integer

    begin

      result := StrToInt(s);

    end;

 

myfunction('abcd');

end;