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');
end, 2000);
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, SmartMS 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 SmartMS. 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;
|
|