www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - D2 closure and loop variables

reply Adam D. Ruppe <destructionator gmail.com> writes:
I'm trying to make a delegate thing in a loop and hit something that seemed
intuitively wrong. Here's some short code that shows what I mean:

===
import std.stdio;

class A {
	this(void delegate() _a) {
		a = _a;
	}

	void run() { a(); }

	private void delegate() a;
}

void main() {
	auto list = ['a', 'b', 'c'];
	A[string] buttons;
	foreach(l; list) {
		buttons[l ~ "\n"] = new A( { writefln("%s", l); } );
	}

	auto a = readln();
	buttons[a].run;
}
===

What happens is no matter what key you press, you always get the output of "c",
whereas at first glance, I'd expect it to echo back the same letter to you.
This makes sense when thinking about it in terms of memory: the delegate
accesses the memory that variable l had assigned to it, which was overwritten
by later loop iterations.

But, while it makes sense when thinking about it, intuitively, I expected that
loop variable to be copied over somewhere, so the delegate would be accessing
its own private copy, not the pointer being overwritten by the loop.


Generally, if a delegate escapes any scope, I expect it to take a snapshot of 
the stack it is referencing at that time to make its private copy at the time
it passes the closing brace.


What I'm asking is:

a) Is my expectation wrong, or does that make sense?
b) If not wrong, should D be doing this now, or is this a bug?
and c) If it isn't a bug, should it be? Copying it on every loop iteration
would have a definite performance penalty, so it isn't remotely free. Moreover,
being able to reference variables that might change later can be likely a good
thing:

[code]
SomeClass a;

foreach(item; collection)
  item.someDelegate = { a.doSomething;}

a = new SomeClass;
[/code]

If it make a private copy inside that loop, someDelegate would be looking at
null the whole time, whereas with the current behaviour, this code works.

So changing it might not even be sane from that perspective.


What do you guys think?
Oct 31 2009
next sibling parent Jason House <jason.james.house gmail.com> writes:
Adam D. Ruppe Wrote:

 I'm trying to make a delegate thing in a loop and hit something that seemed
intuitively wrong. Here's some short code that shows what I mean:

I can confirm that your intuition matches others'. I don't have a link, but I do remember a past discussion about binding where this behavior was discussed.
Oct 31 2009
prev sibling parent "Robert Jacques" <sandford jhu.edu> writes:
On Sat, 31 Oct 2009 20:52:05 -0400, Adam D. Ruppe  
<destructionator gmail.com> wrote:

 I'm trying to make a delegate thing in a loop and hit something that  
 seemed intuitively wrong. Here's some short code that shows what I mean:

 ===
 import std.stdio;

 class A {
 	this(void delegate() _a) {
 		a = _a;
 	}

 	void run() { a(); }

 	private void delegate() a;
 }

 void main() {
 	auto list = ['a', 'b', 'c'];
 	A[string] buttons;
 	foreach(l; list) {
 		buttons[l ~ "\n"] = new A( { writefln("%s", l); } );
 	}

 	auto a = readln();
 	buttons[a].run;
 }
 ===

 What happens is no matter what key you press, you always get the output  
 of "c", whereas at first glance, I'd expect it to echo back the same  
 letter to you. This makes sense when thinking about it in terms of  
 memory: the delegate accesses the memory that variable l had assigned to  
 it, which was overwritten by later loop iterations.

 But, while it makes sense when thinking about it, intuitively, I  
 expected that loop variable to be copied over somewhere, so the delegate  
 would be accessing its own private copy, not the pointer being  
 overwritten by the loop.


 Generally, if a delegate escapes any scope, I expect it to take a  
 snapshot of  the stack it is referencing at that time to make its  
 private copy at the time it passes the closing brace.


 What I'm asking is:

 a) Is my expectation wrong, or does that make sense?
 b) If not wrong, should D be doing this now, or is this a bug?
 and c) If it isn't a bug, should it be? Copying it on every loop  
 iteration would have a definite performance penalty, so it isn't  
 remotely free. Moreover, being able to reference variables that might  
 change later can be likely a good thing:

 [code]
 SomeClass a;

 foreach(item; collection)
   item.someDelegate = { a.doSomething;}

 a = new SomeClass;
 [/code]

 If it make a private copy inside that loop, someDelegate would be  
 looking at null the whole time, whereas with the current behaviour, this  
 code works.

 So changing it might not even be sane from that perspective.


 What do you guys think?

For me, this makes sense and isn't a bug. A closure should be able to mutate it's enclosing scope and be affected by mutation. That said, a enhancement that allowed variables to copied by value, instead of by reference, might be nice. As for your particular issue, since it looks like you're doing GUI stuff, you might want to take a look at DFL. One of the things it does it to has all it's delegates take an object and an args object. As most of DFL's objects support tags, this allows you to retrieve custom information from the delegate arguments (though casting all the time does get ugly)
Oct 31 2009