www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Suggestion: "new delegate" - delegates useable outside of the nesting

reply Burton Radons <burton-radons smocky.com> writes:
I use delegates a lot but the fact that they can only live until the end 
of the nesting function scope means I need to use classes in many cases, 
which is a lot more typing (and fluff telling the autodoc that it's not 
part of the public API, and other problems) when it could be easily 
fixed. The syntax change would be very simple:

	dispatcher.add (new delegate void (Event event) { ... });

And the effect would be simple as well: duplicate the calling context's 
stack to the extent that the delegate can see it and use the duplicated 
pointer as the "this" pointer; otherwise it's a regular nested delegate. 
Five minutes work max, very topical changes, explicit allocation, big 
benefits. Is good!
Jun 02 2006
next sibling parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Burton Radons" <burton-radons smocky.com> wrote in message 
news:e5qkhd$2a1u$1 digitaldaemon.com...
I use delegates a lot but the fact that they can only live until the end of 
the nesting function scope means I need to use classes in many cases, which 
is a lot more typing (and fluff telling the autodoc that it's not part of 
the public API, and other problems) when it could be easily fixed. The 
syntax change would be very simple:

 dispatcher.add (new delegate void (Event event) { ... });

 And the effect would be simple as well: duplicate the calling context's 
 stack to the extent that the delegate can see it and use the duplicated 
 pointer as the "this" pointer; otherwise it's a regular nested delegate. 
 Five minutes work max, very topical changes, explicit allocation, big 
 benefits. Is good!

Haha, funny thing is, I'm implementing this _exact_ feature with virtually the same syntax in a small D-like scripting language I'm writing, although I use 'function' instead of 'delegate' (as there is no distinction between the two in my language). This feature is called "static closures," and is very useful indeed. D's dynamic closures (that is, the context for the nested function exists as long as its owning function doesn't return) are fairly useful, but have the one major downside which you point out. Implementing static closures in D would be an interesting proposition, as they would technically be objects, though implicitly. Then there's the problem with RAII class references. Consider something like: char delegate() createFileReader(char[] fileName) { auto File f = new File(fileName, FileMode.In); return new delegate char() { return f.getc(); }; } This function would return a function that would be used to read (very simply) a file, one character at a time. The problem is that the File reference in the enclosing scope could not be deleted when createFileReader returns. I suppose the way around this would be to disallow using RAII class references inside static closures, but..
Jun 02 2006
parent reply Markus Dangl <danglm in.tum.de> writes:
 Implementing static closures in D would be an interesting proposition, as 
 they would technically be objects, though implicitly.  Then there's the 
 problem with RAII class references.  Consider something like:
 
 char delegate() createFileReader(char[] fileName)
 {
     auto File f = new File(fileName, FileMode.In);
 
     return new delegate char()
     {
         return f.getc();
     };
 }
 
 This function would return a function that would be used to read (very 
 simply) a file, one character at a time.  The problem is that the File 
 reference in the enclosing scope could not be deleted when createFileReader 
 returns.  I suppose the way around this would be to disallow using RAII 
 class references inside static closures, but.. 

You might find my "AdvancedDelegate" library useful, i recently posted it to the announce newsgroup. Using its templates for partial application of functions and delegates, you can do something very similiar to the code above: AdvancedDelegate0!(char) createFileReader(char[] fileName) { auto File f = new File(fileName, FileMode.In); auto readerFunc = AdvancedDelegate( function char(File f) { return f.getc(); } ); return readerFunc(f); }
Jun 14 2006
next sibling parent "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Markus Dangl" <danglm in.tum.de> wrote in message 
news:e6pr8e$28k3$1 digitaldaemon.com...

 You might find my "AdvancedDelegate" library useful, i recently posted it 
 to the announce newsgroup. Using its templates for partial application of 
 functions and delegates, you can do something very similiar to the code 
 above:

I have been eyeing that ;)
Jun 14 2006
prev sibling parent reply Tom S <h3r3tic remove.mat.uni.torun.pl> writes:
Markus Dangl wrote:
 You might find my "AdvancedDelegate" library useful, i recently posted 
 it to the announce newsgroup. Using its templates for partial 
 application of functions and delegates, you can do something very 
 similiar to the code above:
 
 AdvancedDelegate0!(char) createFileReader(char[] fileName)
 {
     auto File f = new File(fileName, FileMode.In);
 
     auto readerFunc = AdvancedDelegate(
         function char(File f)
         {
             return f.getc();
         }
     );
     return readerFunc(f);
 }

Hmm.. what's stopping you from returning a 'normal' delegate, thru return &readerFunc(f).Eval or &readerFunc(f).opCall ? it would make more sense to functions which accept char delegate() and have no idea what AdvancedDelegate0!(char) is :) -- Tomasz Stachowiak /+ a.k.a. h3r3tic +/
Jun 14 2006
parent Markus Dangl <danglm in.tum.de> writes:
 Hmm.. what's stopping you from returning a 'normal' delegate, thru 
 return &readerFunc(f).Eval or &readerFunc(f).opCall ? it would make more 
 sense to functions which accept char delegate() and have no idea what 
 AdvancedDelegate0!(char) is :)

The Eval() method is there to really do the evaluation of the delegate. You can easily "convert" an AdvancedDelegate to a delegate by referencing its Eval() method: char delegate() createFileReader(char[] fileName) { File f = new File(fileName, FileMode.In); auto readerFunc = AdvancedDelegate( function char(File f) { return f.getc(); } ); return &(readerFunc(f).Eval); } I see that this isn't very intuitive, so i'm going to add a "GetDelegate" method and overload the "opCast" operator - you can then also write: return readerFunc(f).GetDelegate; or: return cast(char delegate()) (readerFunc(f));
Jun 15 2006
prev sibling parent reply Walter Bright <newshound digitalmars.com> writes:
Burton Radons wrote:
 I use delegates a lot but the fact that they can only live until the end 
 of the nesting function scope means I need to use classes in many cases, 
 which is a lot more typing (and fluff telling the autodoc that it's not 
 part of the public API, and other problems) when it could be easily 
 fixed. The syntax change would be very simple:
 
     dispatcher.add (new delegate void (Event event) { ... });
 
 And the effect would be simple as well: duplicate the calling context's 
 stack to the extent that the delegate can see it and use the duplicated 
 pointer as the "this" pointer; otherwise it's a regular nested delegate. 
 Five minutes work max, very topical changes, explicit allocation, big 
 benefits. Is good!

Unfortunately, it's quite a bit more work than that. You have to copy all the frames of the lexically enclosing functions, too. Then you have the issue of updates to the framed variable copies not being reflected in the stack variables. Then there's the problem of things like: int x; int* px = &x;
Jun 02 2006
next sibling parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Walter Bright" <newshound digitalmars.com> wrote in message 
news:e5qli2$2ale$1 digitaldaemon.com...

 Then you have the issue of updates to the framed variable copies not being 
 reflected in the stack variables.

This, at least, seems to be a requirement of static closures. In Lua, which supports them, when you instantiate a static closure, the values it uses from enclosing scopes ("upvalues") are "frozen" - they are copied into the closure's instance. Then, updating the value using the closure doesn't affect the original variable. This is an important feature so that multiple instances of closures can be created which don't step on each others' toes, so to speak.
Jun 02 2006
parent Walter Bright <newshound digitalmars.com> writes:
Jarrett Billingsley wrote:
 "Walter Bright" <newshound digitalmars.com> wrote in message 
 news:e5qli2$2ale$1 digitaldaemon.com...
 
 Then you have the issue of updates to the framed variable copies not being 
 reflected in the stack variables.

This, at least, seems to be a requirement of static closures. In Lua, which supports them, when you instantiate a static closure, the values it uses from enclosing scopes ("upvalues") are "frozen" - they are copied into the closure's instance. Then, updating the value using the closure doesn't affect the original variable. This is an important feature so that multiple instances of closures can be created which don't step on each others' toes, so to speak.

Sure, you can design the language that way. But I've had uses for ones that *did* update the local variables.
Jun 02 2006
prev sibling parent reply Burton Radons <burton-radons smocky.com> writes:
Walter Bright wrote:

 Burton Radons wrote:
 
 I use delegates a lot but the fact that they can only live until the 
 end of the nesting function scope means I need to use classes in many 
 cases, which is a lot more typing (and fluff telling the autodoc that 
 it's not part of the public API, and other problems) when it could be 
 easily fixed. The syntax change would be very simple:

     dispatcher.add (new delegate void (Event event) { ... });

 And the effect would be simple as well: duplicate the calling 
 context's stack to the extent that the delegate can see it and use the 
 duplicated pointer as the "this" pointer; otherwise it's a regular 
 nested delegate. Five minutes work max, very topical changes, explicit 
 allocation, big benefits. Is good!

Unfortunately, it's quite a bit more work than that. You have to copy all the frames of the lexically enclosing functions, too. Then you have the issue of updates to the framed variable copies not being reflected in the stack variables. Then there's the problem of things like: int x; int* px = &x;

Good points (I forgot about multiple nesting - did I ever compliment you for putting that in with the first release containing nested functions? - but you need to chain anyway so the logic must already be in there to piggyback onto), it is more work but compare it to the work required when it's omitted. I have done terrible things, committed sins against good engineering just because my nesting scope exits, and while it hasn't had a cooling effect on me, I'm sure there have been other people who have resorted to inferior Java class-inheritance techniques just to get around this problem. I have to admit I've done it once or twice. I was young and naive! Frozen variables are, of course, a feature, not a bug, and one the user would opt into by using the "new" variant. We discussed this in 2003. ;-) Anyway in three years and four months I haven't depended on the nesting scope's variables varying once, and while I haven't really wanted them to be frozen either, I've used it a few times in languages which freeze the nesting scopes (like Python). The only really good argument against this originally, in my opinion, was that it was a hidden allocation, but it's explicit like this.
Jun 02 2006
parent reply Walter Bright <newshound digitalmars.com> writes:
Burton Radons wrote:
 Walter Bright wrote:
 Unfortunately, it's quite a bit more work than that. You have to copy 
 all the frames of the lexically enclosing functions, too. Then you 
 have the issue of updates to the framed variable copies not being 
 reflected in the stack variables. Then there's the problem of things 
 like:

   int x;
   int* px = &x;

Good points (I forgot about multiple nesting - did I ever compliment you for putting that in with the first release containing nested functions? - but you need to chain anyway so the logic must already be in there to piggyback onto), it is more work but compare it to the work required when it's omitted. I have done terrible things, committed sins against good engineering just because my nesting scope exits, and while it hasn't had a cooling effect on me, I'm sure there have been other people who have resorted to inferior Java class-inheritance techniques just to get around this problem. I have to admit I've done it once or twice. I was young and naive! Frozen variables are, of course, a feature, not a bug, and one the user would opt into by using the "new" variant. We discussed this in 2003. ;-) Anyway in three years and four months I haven't depended on the nesting scope's variables varying once, and while I haven't really wanted them to be frozen either, I've used it a few times in languages which freeze the nesting scopes (like Python). The only really good argument against this originally, in my opinion, was that it was a hidden allocation, but it's explicit like this.

What about the px problem above? This wouldn't occur in Python which doesn't have pointers, but it sure happens in D.
Jun 02 2006
parent reply Burton Radons <burton-radons smocky.com> writes:
Walter Bright wrote:

 Burton Radons wrote:
 
 Walter Bright wrote:

 Unfortunately, it's quite a bit more work than that. You have to copy 
 all the frames of the lexically enclosing functions, too. Then you 
 have the issue of updates to the framed variable copies not being 
 reflected in the stack variables. Then there's the problem of things 
 like:

   int x;
   int* px = &x;

Good points (I forgot about multiple nesting - did I ever compliment you for putting that in with the first release containing nested functions? - but you need to chain anyway so the logic must already be in there to piggyback onto), it is more work but compare it to the work required when it's omitted. I have done terrible things, committed sins against good engineering just because my nesting scope exits, and while it hasn't had a cooling effect on me, I'm sure there have been other people who have resorted to inferior Java class-inheritance techniques just to get around this problem. I have to admit I've done it once or twice. I was young and naive! Frozen variables are, of course, a feature, not a bug, and one the user would opt into by using the "new" variant. We discussed this in 2003. ;-) Anyway in three years and four months I haven't depended on the nesting scope's variables varying once, and while I haven't really wanted them to be frozen either, I've used it a few times in languages which freeze the nesting scopes (like Python). The only really good argument against this originally, in my opinion, was that it was a hidden allocation, but it's explicit like this.

What about the px problem above? This wouldn't occur in Python which doesn't have pointers, but it sure happens in D.

What about if the user calls a nested function outside of the nesting scope? They both have pitfalls. I don't really want to mention it, but if you invert the logic it continues to act the same without this pitfall; the only change is that you can call the nested function outside of the nesting scope. What I mean is that instead of just passing the delegate a pointer to the frame, allocate the frame at the start if it or a nested scope has a "new delegate" and use that for both the delegate and that scope: float bar = 6; void delegate () dg = new delegate void () { bar = 8; } Becomes: struct Delegate { void *self, func; } struct Frame { float bar; Delegate dg; } Frame *frame = new Frame; Delegate dg; frame.bar = 6; frame.dg = Delegate { frame, function void (Frame *frame) { frame.bar = 8; } }; Oh no, it's all syntax sugar! :-) The logic for doing this should mostly already be in the code. This might be well-justified because it makes nested functions behave consistently, and with some more effort it would only allocate a frame containing variables the nesting functions actually use.
Jun 03 2006
parent reply Tom S <h3r3tic remove.mat.uni.torun.pl> writes:
Burton Radons wrote:
 (...)

Please, don't use suspicious whitespace sequences ! LOL http://158.75.59.9/~h3/tmp/lolAtAvast.png
Jun 03 2006
parent Daniel Keep <daniel.keep.lists gmail.com> writes:
Tom S wrote:
 Burton Radons wrote:
 (...)

Please, don't use suspicious whitespace sequences ! LOL http://158.75.59.9/~h3/tmp/lolAtAvast.png

Wow. That's one touchy virus scanner. I never knew that whitespace was so dangerous (well, I suppose if you feed to the http://en.wikipedia.org/wiki/Whitespace_programming_language interpreter, it might be). -- Daniel -- Unlike Knuth, I have neither proven or tried the above; it may not even make sense. v2sw5+8Yhw5ln4+5pr6OFPma8u6+7Lw4Tm6+7l6+7D i28a2Xs3MSr2e4/6+7t4TNSMb6HTOp5en5g6RAHCP http://hackerkey.com/
Jun 03 2006