Anonymous Method constraints
Anonymous Method constraints
Due to the occasional shooting of foot due to how capture works, I wish there was a way of optionally constraining references for an anonymous method, so that I could enforce local references only.
type
TFilterMethod = constrained reference to function (const Item:T):Boolean;
TInstanceList = class
property function Filtered(Filter: TFilterMethod):TInstanceList;
end;
var
SomeRef: TQualifier;
List: TInstanceList;
Element: TThing;
begin
...
// compiles
for Element in List.Filtered(function (Item: TThing)
begin
Result := Item.Qualifier in [This, That]);
end)
do begin
...
// should not compile - but error on "reference outside method not allowed for constrained methods
for Element in List.Filtered(function (Item: TThing)
begin
Result := (Item.Qualifier = SomeRef);
end)
do begin
...
Due to the occasional shooting of foot due to how capture works, I wish there was a way of optionally constraining references for an anonymous method, so that I could enforce local references only.
type
TFilterMethod
TInstanceList
end;
var
SomeRef: TQualifier;
List: TInstanceList
Element: TThing;
begin
...
// compiles
for Element in List.Filtered(function (Item: TThing)
begin
Result := Item.Qualifier in [This, That]);
end)
do begin
...
// should not compile - but error on "reference outside method not allowed for constrained methods
for Element in List.Filtered(function (Item: TThing)
begin
Result := (Item.Qualifier = SomeRef);
end)
do begin
...
Could you explain further please? Why would you want the second example to not compile - isn't a reference it an item in a list perfectly valid? (Or is it something to do with the property Filtered being a property not function? I've never seen property syntax like that before - it's not an indexed property, it has normal braces like a method?)
ReplyDeleteIf you don't like variable capturing then stick to plain old function pointers (sure you cannot write the code inline but have to pre-define the routine)
ReplyDeleteI think he means that the second example should fail because the anonymous method captures are variable (SomeRef). The first example dont need any variable capturing...
ReplyDeleteBut isnt that an advantage of anonymous method? Capturing variables from outside?
If you don't want anonymous methods to capture variables, don't refer to variables outside the anonymous method's scope.
ReplyDeleteDavid Millington My bad: Should be function, not property.
ReplyDeleteStefan Glienke - I could do that, but it is a lot less flexible, and it can take miniscule pieces of logic of it's context.
David Heffernan - Duh, Captain Obvious! That advice has a Microsoft help quality about it: Accurate, but useless.
Fabian S. Biehn - Capturing is an advantage - but it can also be a pitfall. In some cases - you capture only the initial value of something that should have changed later.
It would be nice to be able to constrain the abstract method definition by design.
"In some cases - you capture only the initial value of something that should have changed later."
ReplyDeleteActually it is the opposite - variable capturing means you always get the most recent value of this variable. Capturing means by reference and not by value. If you don't want that then write a function that returns the anonymous method or use the specification pattern (see Spring.Designpatterns.pas)
Lars Fosdal I don't think the advice is useless. I think it's important to know your tools and understand the code that you write. Certainly when I write anonymous methods, I think very carefully about what, if anything, is being captured. Do you not?
ReplyDeleteIt seems to me that you are responding to being caught out by your own incomplete understanding of how variable capture works and think that a change to the language is the fix.
Furthermore, your proposed fix would be the absolute worst way to tackle this. You'd condemn any users of that type to be unable to use variable capture. If a change were to be made, and I see no reason to make one, but if one were to be made you would make it at the point at which the anonymous method was defined. So you'd have the programmer state in the definition of the anonymous method that the method did not capture any variables. And then the compiler could verify that.
Stefan Glienke "Actually it is the opposite - variable capturing means you always get the most recent value of this variable."
ReplyDeleteCapturing a mutable variable by reference is a hell... I'd rather wish to always capture by value, not by reference.
Roman Yankovsky "I'd rather wish to always capture by value, not by reference."
ReplyDeleteThat would actually cripple this feature. If you want capture value then make a local variable, assign the mutable variable to it and you are done (as you have to do with certain types like records).
Wow, some pretty far out views of capture being expressed in this thread!!
ReplyDeleteStefan Glienke this feature came from functional languages where all variables are immutable by default. Capturing by value cannot cripple this feature :)
ReplyDeleteDisclaimer: I know, that some times is accessible only by a reference. That's why Emba didn't have a choice.
I guess the person that wrote this code also did not understand variable capturing: http://rosettacode.org/wiki/Closures/Value_capture#Delphi facepalm (Tip: look at the comment in this code: http://rosettacode.org/wiki/Closures/Value_capture#Using_delegates_only)
ReplyDeleteStefan Glienke Slightly tricky to reproduce the C# code (the second example) in Delphi because Delphi can't readily create local variables in the same way that C family languages do by declaring them inside a loop body.
ReplyDeleteDavid Heffernan - It is not a "fix", it is a constraint - such as setting a field to be strict private. It gives me, as the class designer, the option to constrain the use and enforce a design.
ReplyDeleteIf you design a class that uses anon methods, it is all well and fine that you do the required thinking, but you can't enforce this for those that will be using your designs.
I really would like to have more granular control of the capture requirements - i.e. to, in the procedure in which the anon method is declared, discern the constraint between "simple" local variable captures and referenced object/property value or reference captures - but I can't think of an easy way to introduce the constraint.
If it needs to be done where you implement the anon method - as you suggest - then the type declaration needs a way to say that the compiler must require a capture constraint declaration at the implementation.
Fixed the code on rosettacode :)
ReplyDeleteLars Fosdal You are looking at it from a wrong perspective. A delegate type is an interface (even literally) or contract. If you are using variable capturing or not is an implementation detail of that anonymous method and should not be constrained by the interface.
ReplyDeleteStefan Glienke - In principle, I agree. Perhaps I just need a huge hint about the use of references outside the anon method?
ReplyDelete+Lars What Stefan just said is my point about where the constraint would be specified. The consumer of an anon method has no grounds to care about its implementation. Which is why you'll never see the feature you asked for.
ReplyDeleteWhat you actually looking for is a tool to help you find your mistakes.
David Heffernan What I would like is a means to help those that use the code of others, avoid making such mistakes.
ReplyDeleteI've made the mistakes, and learned from them - but if you use code with Generics, without the experience - you won't know the pitfalls until you spend time digging them out in the debugger.
+Lars Changing the language is quite drastic. To help novices avoid novice mistakes? Not enough justification. You'd be adding clutter for an ephemeral gain. That's a net loss.
ReplyDeleteLars Fosdal We desperately need (better) static code analysis tools. It could tell you in a blink of an eye where you are referring something that you don't want.
ReplyDeleteHints and warnings serve a similar purpose.
ReplyDeleteNot initializing a variable is a typical novice mistake, right?
Changing the language is indeed drastic, and it may add clutter - but the challenge is not ephemeral.
+Lars Hints and warnings don't involve language changes. Static analysis, external to the language, is how to deal with such issues.
ReplyDeleteStefan Glienke , Lars Fosdal "We desperately need (better) static code analysis tools." So, hi, Roman Yankovsky . I'm sure you're watching this thread ;)
ReplyDeleteLars Fosdal I really don't understand why the Filtered function would care that SomeRef is being captured. What's wrong with that?
ReplyDeleteAsbjørn Heid It's perhaps not the best of examples. The original thought was to prevent "bad captures" (i.e. value instead of value reference). Stefan Glienke / David Heffernan had compelling arguments for why my suggestion was the wrong solution.
ReplyDeleteThe concept can have potential as a way to ensure that the anon method has no side effects - but, yeah - not for the original thought.
Lars Fosdal Yeah ok. I could understand why the caller would consider it bad, but not Filtered.
ReplyDeleteFor what it's worth, in C++[1] you can specify exactly which and how variables are captured. Of course only when you declare the lambda, whatever is using the lambda can't, and shouldn't care.
The syntax is a bit fugly, but at least you get absolute control. Want to make sure no variables are capture? No problem. Want to auto-capture variables by value, except "foo" by reference? Sure thing.
[1]: http://en.cppreference.com/w/cpp/language/lambda
Asbjørn Heid Thanks for that info - I feel less bonkers now ;)
ReplyDeleteAs is so often the case with C++, the flexibility is both powerful and confusing! Pity those poor people who have to write the compilers!
ReplyDeleteLars Fosdal Don't be hasty! ;)
ReplyDelete