Nice articles. Hadn't seen those. One question, Gunny - why the bit.ly link, and not just the actual link? That would give a nice preview in the post as well.
There's some debate on the distinction between lambdas and closures, even though there appears to be lots of clear academic distinctions. It's the general concept that's most important here, not the more subtle differences.
Basically, what I'm pointing at in the presentation is the use of anonymous methods passed as parameters to other methods. Call-backs as it were.
It's easier to pass them as parameters inline than to declare them separately then pass them by reference. And it's easier to read the code that way as well.
They can feel a little awkward when you first start using them. It helped me a lot to read several books on Java 8 to get the gist of how broadly they can be used. Delphi's libraries don't do squat with them. You need to look elsewhere for examples. (Frankly, I don't know what possessed the Delphi language team to add them to the language, because they don't seem to be leveraged at all, at least in the public RTL interfaces. Maybe inside where they're not visible without a lot of digging.)
From Wikipedia: In computer programming, an anonymous function (also function literal or lambda abstraction) is a function definition that is not bound to an identifier.
That's a solid definition for lambda.
Closure
From Wikipedia: In programming languages, a closure (also lexical closure or function closure) is a function or reference to a function together with a referencing environment—a table storing a reference to each of the non-local variables (also called free variables or upvalues) of that function.[1] A closure—unlike a plain function pointer—enables a function to access those non-local variables even when invoked outside its immediate lexical scope.
This is nice, but it's not terribly helpful for people who are just trying to get the gist of why you'd use anonymous methods as inline "parameters" to other method calls in Delphi rather than declare them separately and pass them as method pointers the way standard event handling works.
The subtlety is in how you access variables from inside the bodies of the anonymous call-back methods on the client side, and has nothing to do with WHY or HOW you'd use inline call-back methods in the first place. It's irrelevant in the context of call-back methods that follow the pattern of standard event handlers, which can be confusing to people who think it's just a simple matter of substituting an out-of-line method for an in-line method.
Honestly, I learned more reading about this topic in a couple of Java 8 books than anything I found discussing Delphi. It's sad that we have to go to a third-party source to learn details about how things work in Delphi.
It seems you mostly can find anonymous methods in the newer parts of the XE7 lib sources, but there is a few in the modernized rtl/common units as well.
type = reference to function - 59 occurrences data\datasnap\Datasnap.DSClientRest.pas data\datasnap\Datasnap.DSSession.pas data\dbx\Data.DBXJSONReflect.pas data\rest\REST.JsonReflect.pas databinding\components\Data.Bind.Components.pas databinding\components\Data.Bind.ObjectScope.pas databinding\engine\System.Bindings.EvalProtocol.pas databinding\engine\System.Bindings.EvalSys.pas databinding\engine\System.Bindings.Methods.pas databinding\engine\System.Bindings.Outputs.pas databinding\graph\GraphView.pas fmx\FMX.Dialogs.pas fmx\FMX.Types.pas internet\Web.WebFileDispatcher.pas rtl\common\System.Classes.pas rtl\common\System.Generics.Collections.pas rtl\common\System.Generics.Defaults.pas rtl\common\System.IOUtils.pas rtl\common\System.Sensors.pas rtl\common\System.Zip.pas rtl\sys\System.SysUtils.pas vcl\Vcl.Dialogs.pas
An example of when anonymous methods can be better than procedure of object:
I was cussing at some TFDScript FireDAC event handlers the other day - which are procedure of object - as I didn't want to create an event handler in my FireDAC wrapper base class, as it only would be used by a single specific method, related to running scripts. The event handler in question captures spool output from the "db console" when running a script.
I ended up creating a proxy object in the descendent class, which had an anon method property, which I could pass along with the descendent class. If the handler had been reference to procedure, I wouldn't have had to do this proxying.
Schrott!
ReplyDeleteall is string?!?
a type, is a type, is a type!
Nice articles. Hadn't seen those.
ReplyDeleteOne question, Gunny - why the bit.ly link, and not just the actual link? That would give a nice preview in the post as well.
I just started using twitter and it's become a habit. I'll keep that in mind going forward. Thanks Lars.
ReplyDeleteAh, that explains it :)
ReplyDeleteI do have a Twitter account as well, but I largely find Twitter to have an intolerable content to noise ratio :P
Twitter probably does. However, I keep coming back to...
ReplyDelete"The unexamined life is not worth living."
Socrates 469 BC-399 BC
In his times, people gathered to "tweet" :)
ReplyDeleteHey, thanks guys! What did you find most informative about it? Anything you'd like to see elaborated upon?
ReplyDeleteDavid Schwartz I'd like to know what you mean by Lamdas and Closures in simple terms that Forrest Gump would understand.
ReplyDelete
ReplyDeleteThere's some debate on the distinction between lambdas and closures, even though there appears to be lots of clear academic distinctions. It's the general concept that's most important here, not the more subtle differences.
Basically, what I'm pointing at in the presentation is the use of anonymous methods passed as parameters to other methods. Call-backs as it were.
It's easier to pass them as parameters inline than to declare them separately then pass them by reference. And it's easier to read the code that way as well.
They can feel a little awkward when you first start using them. It helped me a lot to read several books on Java 8 to get the gist of how broadly they can be used. Delphi's libraries don't do squat with them. You need to look elsewhere for examples. (Frankly, I don't know what possessed the Delphi language team to add them to the language, because they don't seem to be leveraged at all, at least in the public RTL interfaces. Maybe inside where they're not visible without a lot of digging.)
Lambda
ReplyDeleteFrom Wikipedia: In computer programming, an anonymous function (also function literal or lambda abstraction) is a function definition that is not bound to an identifier.
That's a solid definition for lambda.
Closure
From Wikipedia: In programming languages, a closure (also lexical closure or function closure) is a function or reference to a function together with a referencing environment—a table storing a reference to each of the non-local variables (also called free variables or upvalues) of that function.[1] A closure—unlike a plain function pointer—enables a function to access those non-local variables even when invoked outside its immediate lexical scope.
Again, a pretty solid definition.
This is nice, but it's not terribly helpful for people who are just trying to get the gist of why you'd use anonymous methods as inline "parameters" to other method calls in Delphi rather than declare them separately and pass them as method pointers the way standard event handling works.
ReplyDeleteThe subtlety is in how you access variables from inside the bodies of the anonymous call-back methods on the client side, and has nothing to do with WHY or HOW you'd use inline call-back methods in the first place. It's irrelevant in the context of call-back methods that follow the pattern of standard event handlers, which can be confusing to people who think it's just a simple matter of substituting an out-of-line method for an in-line method.
Honestly, I learned more reading about this topic in a couple of Java 8 books than anything I found discussing Delphi. It's sad that we have to go to a third-party source to learn details about how things work in Delphi.
David Schwartz Michael asked what they were.
ReplyDeleteIt seems you mostly can find anonymous methods in the newer parts of the XE7 lib sources, but there is a few in the modernized rtl/common units as well.
ReplyDeletetype = reference to function - 59 occurrences
data\datasnap\Datasnap.DSClientRest.pas
data\datasnap\Datasnap.DSSession.pas
data\dbx\Data.DBXJSONReflect.pas
data\rest\REST.JsonReflect.pas
databinding\components\Data.Bind.Components.pas
databinding\components\Data.Bind.ObjectScope.pas
databinding\engine\System.Bindings.EvalProtocol.pas
databinding\engine\System.Bindings.EvalSys.pas
databinding\engine\System.Bindings.Methods.pas
databinding\engine\System.Bindings.Outputs.pas
databinding\graph\GraphView.pas
fmx\FMX.Dialogs.pas
fmx\FMX.Types.pas
internet\Web.WebFileDispatcher.pas
rtl\common\System.Classes.pas
rtl\common\System.Generics.Collections.pas
rtl\common\System.Generics.Defaults.pas
rtl\common\System.IOUtils.pas
rtl\common\System.Sensors.pas
rtl\common\System.Zip.pas
rtl\sys\System.SysUtils.pas
vcl\Vcl.Dialogs.pas
type = reference to procedure - 81 occurrences
Experts\ExpertsModuleCreators.pas
data\cloud\DSAzDlgPutBlockList.pas
data\cloud\DSAzure.pas
data\cloud\DSAzureBlob.pas
data\cloud\de\DSAzDlgPutBlockList.pas
data\cloud\fr\DSAzDlgPutBlockList.pas
data\cloud\ja\DSAzDlgPutBlockList.pas
data\datasnap\Datasnap.DSClientRest.pas
data\datasnap\Datasnap.DSCommon.pas
data\datasnap\Datasnap.DSServer.pas
data\datasnap\Datasnap.DSService.pas
data\datasnap\Datasnap.DSSession.pas
data\dbx\Data.DBXCommon.pas
data\dbx\Data.DBXJSONReflect.pas
data\ems\EMS.ResourceTypes.pas
data\rest\REST.Backend.EMSApi.pas
data\rest\REST.Backend.KinveyApi.pas
data\rest\REST.Backend.ParseAPI.pas
data\rest\REST.Backend.ServiceTypes.pas
data\rest\REST.JsonReflect.pas
databinding\components\Data.Bind.Components.pas
databinding\engine\System.Bindings.CustomScope.pas
databinding\engine\System.Bindings.EvalProtocol.pas
databinding\engine\System.Bindings.Outputs.pas
fmx\FMX.Controls.pas
fmx\FMX.Dialogs.pas
fmx\FMX.Helpers.Android.pas
fmx\FMX.Platform.pas
rtl\common\System.Actions.pas
rtl\common\System.Classes.pas
rtl\common\System.Messaging.pas
rtl\common\System.Rtti.pas
rtl\common\System.Threading.pas
rtl\common\System.Win.ComObj.pas
rtl\sys\System.SysUtils.pas
vcl\Vcl.Touch.GestureCtrls.pas
An example of when anonymous methods can be better than procedure of object:
ReplyDeleteI was cussing at some TFDScript FireDAC event handlers the other day - which are procedure of object - as I didn't want to create an event handler in my FireDAC wrapper base class, as it only would be used by a single specific method, related to running scripts. The event handler in question captures spool output from the "db console" when running a script.
I ended up creating a proxy object in the descendent class, which had an anon method property, which I could pass along with the descendent class. If the handler had been reference to procedure, I wouldn't have had to do this proxying.
(The code is abbreviated)
TScriptIntercept = class
private
FOnOutput: TAnonParamProc;
public
procedure OnScriptSpoolPut(AEngine: TFDScript; const AMessage: string; AKind: TFDScriptOuputKind);
property OnOutput: TAnonParamProc read FOnOutput write SetOnOutput;
end;
procedure TScriptIntercept.OnScriptSpoolPut(AEngine: TFDScript;
const AMessage: string; AKind: TFDScriptOuputKind);
begin
FOnOutput(AMessage);
end;
function TPSDDatabase_FD.ExecuteScript(const aSQL: TStrings;
const aOnOutput: TAnonParamProc): Integer;
var
rc : Integer;
Script: TFDScript;
ScriptInterceptor : TScriptIntercept;
begin
Script := CreateScript;
ScriptInterceptor := TScriptIntercept.Create;
ScriptInterceptor.OnOutput := aOnOutput;
Script.OnSpoolPut := ScriptInterceptor.OnScriptSpoolPut;
...