digitalmars.D.learn - closures
- Jens (55/55) Jul 16 2009 Hello there,
- Jarrett Billingsley (11/66) Jul 16 2009 mpilers translate things, especially in C++. So I appreciate the difficu...
- BCS (4/8) Jul 16 2009 This will quickly devolve into the general escape analysts problem and h...
- Jarrett Billingsley (11/19) Jul 16 2009 At least for non-ref-param value types, I don't think it's
- Steven Schveighoffer (8/31) Jul 16 2009 What about syntax to force closure behavior (on or off)? Not that I hav...
- Jarrett Billingsley (20/24) Jul 16 2009 ve
- Steven Schveighoffer (22/33) Jul 16 2009 Yes I'm aware. But that has some limitations.
- bearophile (72/79) Jul 16 2009 I have run your code with DMD v2.031 on Windows, and it prints:
- Jarrett Billingsley (4/18) Jul 16 2009 Strange. I use 2.031 on Windows too, and I get strange values for the
Hello there, I'm new to D and experimenting with closures. I know the basics of how compilers translate things, especially in C++. So I appreciate the difficulty of implementing closures correctly and been wondering if it really works. I found a place where dmd 2 appears to fail, but I don't know whether that's a bug or just not supported: import std.stdio; struct Foo { int a = 7; int bar() { return a; } int delegate() makedelegate() { int abc() { return a; } return &abc; } } void call(int delegate() dg) { writefln("foo: %d", dg()); } int delegate() makedelegate1() { int x = 27; int abc() { return x; } return &abc; } int delegate() makedelegate2() { Foo f; int abc() { return f.a; } return &abc; } int delegate() makedelegate3() { Foo f; return &f.bar; } int delegate() makedelegate4b(ref Foo f) { int abc() { return f.a; } return &abc; } int delegate() makedelegate4() { Foo f; return makedelegate4b(f); } void main(string[] args) { // On dmd v2.029, linux build, this... call(makedelegate1()); // ...works: 27 call(makedelegate2()); // ...works: 7 call(makedelegate3()); // ...doesn't work: 134518855 call(makedelegate4()); // ...doesn't work: 134518947 Foo f; call(&f.bar); // ...works: 7 } In case 4 the reference is explicit, so it's somehow easier to see that something dangerous is being done, but in case 3, D seems to make it too easy to shoot yourself in the foot. Is there a resource discussing these issues?
Jul 16 2009
On Thu, Jul 16, 2009 at 8:02 AM, Jens<jens-theisen-tmp01 gmx.de> wrote:Hello there, I'm new to D and experimenting with closures. I know the basics of how co=mpilers translate things, especially in C++. So I appreciate the difficulty= of implementing closures correctly and been wondering if it really works. = I found a place where dmd 2 appears to fail, but I don't know whether that'= s a bug or just not supported:import std.stdio; struct Foo { =A0int a =3D 7; =A0int bar() { return a; } =A0int delegate() makedelegate() { =A0 =A0int abc() { return a; } =A0 =A0return &abc; =A0} } void call(int delegate() dg) { =A0writefln("foo: %d", dg()); } int delegate() makedelegate1() { =A0int x =3D 27; =A0int abc() { return x; } =A0return &abc; } int delegate() makedelegate2() { =A0Foo f; =A0int abc() { return f.a; } =A0return &abc; } int delegate() makedelegate3() { =A0Foo f; =A0return &f.bar; } int delegate() makedelegate4b(ref Foo f) { =A0int abc() { return f.a; } =A0return &abc; } int delegate() makedelegate4() { =A0Foo f; =A0return makedelegate4b(f); } void main(string[] args) { =A0// On dmd v2.029, linux build, this... =A0call(makedelegate1()); // ...works: 27 =A0call(makedelegate2()); // ...works: 7 =A0call(makedelegate3()); // ...doesn't work: 134518855 =A0call(makedelegate4()); // ...doesn't work: 134518947 =A0Foo f; =A0call(&f.bar); // ...works: 7 } In case 4 the reference is explicit, so it's somehow easier to see that s=omething dangerous is being done, but in case 3, D seems to make it too eas= y to shoot yourself in the foot.Is there a resource discussing these issues?Yeah, you've found it :) I think what's going on here is that the compiler will *only* allocate closures for nested functions. However allocating a closure for a delegate of a value object would be a nice addition.
Jul 16 2009
Reply to Jarrett,I think what's going on here is that the compiler will *only* allocate closures for nested functions. However allocating a closure for a delegate of a value object would be a nice addition.This will quickly devolve into the general escape analysts problem and here there be dragons. I think the correct solution is to say it's unsupported by calling this an escaping reference bug in the user code.
Jul 16 2009
On Thu, Jul 16, 2009 at 1:24 PM, BCS<ao pathlink.com> wrote:Reply to Jarrett,At least for non-ref-param value types, I don't think it's unreasonable to say that &obj.func should allocate a closure. I mean, it's the same as saying int delegate() dg; dg.funcptr = &typeof(obj).func; dg.ptr = &obj; and the last line there would be illegal to return under normal circumstances anyway. But you're probably right that it might just be easier to disallow it and force people to write { return f.func(); } instead. :)I think what's going on here is that the compiler will *only* allocate closures for nested functions. However allocating a closure for a delegate of a value object would be a nice addition.This will quickly devolve into the general escape analysts problem and here there be dragons. I think the correct solution is to say it's unsupported by calling this an escaping reference bug in the user code.
Jul 16 2009
On Thu, 16 Jul 2009 13:34:27 -0400, Jarrett Billingsley <jarrett.billingsley gmail.com> wrote:On Thu, Jul 16, 2009 at 1:24 PM, BCS<ao pathlink.com> wrote:What about syntax to force closure behavior (on or off)? Not that I have any to suggest, but if the compiler is going to remain ignorant about escape analysis, then we should be able to supply the intelligence, and hacking an extra wrapper function to force behavior seems... well, hackish :) -SteveReply to Jarrett,At least for non-ref-param value types, I don't think it's unreasonable to say that &obj.func should allocate a closure. I mean, it's the same as saying int delegate() dg; dg.funcptr = &typeof(obj).func; dg.ptr = &obj; and the last line there would be illegal to return under normal circumstances anyway. But you're probably right that it might just be easier to disallow it and force people to write { return f.func(); } instead. :)I think what's going on here is that the compiler will *only* allocate closures for nested functions. However allocating a closure for a delegate of a value object would be a nice addition.This will quickly devolve into the general escape analysts problem and here there be dragons. I think the correct solution is to say it's unsupported by calling this an escaping reference bug in the user code.
Jul 16 2009
On Thu, Jul 16, 2009 at 2:22 PM, Steven Schveighoffer<schveiguy yahoo.com> wrote:What about syntax to force closure behavior (on or off)? =A0Not that I ha=veany to suggest, but if the compiler is going to remain ignorant about esc=apeanalysis, then we should be able to supply the intelligence, and hacking =anextra wrapper function to force behavior seems... well, hackish :)There already is, at least for turning closures off. A common design pattern in D1 is to pass the address of a nested function as a callback. This is convenient and performant: void putChar(char c) { write(c); } std.format.doFormat(&putChar, blahblahblah); // or so If D2 were to allocate a closure for &putChar, suddenly your formatting function that's called several hundred times a second starts eating memory. The solution is to put 'scope' on the parameter: void doFormat(scope void delegate(char) dg, blahblah) { .. } DMD2 will not allocate a closure for the initial code if 'scope' is present, and if removed, it will.
Jul 16 2009
On Thu, 16 Jul 2009 14:56:28 -0400, Jarrett Billingsley <jarrett.billingsley gmail.com> wrote:On Thu, Jul 16, 2009 at 2:22 PM, Steven Schveighoffer<schveiguy yahoo.com> wrote:Yes I'm aware. But that has some limitations. For example, if I truly want my delegate to be a variable, I have to decide which delegate to use at the time I'm calling the function. I can't store the delegate and then call the function later. Also, forcing a closure for the reasons discussed in this thread would be useful. I think the philosophy that D has on say const would be beneficial here: 1. When the compiler can prove the code is correct, allow it. e.g. implicit cast of a const value type to a mutable value type. 2. When the compiler can't prove, do the safest thing. e.g. e.g. const could be a pointer to mutable, but compiler isn't sure, so don't allow implicit cast. 3. When the user knows more about the situation than the compiler, allow override. e.g. explicit cast away const is allowed, or explicit cast to immutable. The same thing could be applied to closures: 1. Calling a function with a delegate where the arg is scope is allowed. 2. Assigning a delegate to a variable causes a closure. 3. The user can specify that a delegate does not need a closure. -SteveWhat about syntax to force closure behavior (on or off)? Not that I have any to suggest, but if the compiler is going to remain ignorant about escape analysis, then we should be able to supply the intelligence, and hacking an extra wrapper function to force behavior seems... well, hackish :)There already is, at least for turning closures off.
Jul 16 2009
Jens:// On dmd v2.029, linux build, this... call(makedelegate1()); // ...works: 27 call(makedelegate2()); // ...works: 7 call(makedelegate3()); // ...doesn't work: 134518855 call(makedelegate4()); // ...doesn't work: 134518947 Foo f; call(&f.bar); // ...works: 7I have run your code with DMD v2.031 on Windows, and it prints: foo: 27 foo: 7 foo: 7 foo: 7 foo: 7 That sounds too much good, probably the right values are left in the stack. So I have added some printf() that return an int and show better the problems: import std.stdio: writefln, printf; struct Foo { int a = 7; int bar() { return a; } int delegate() makeDelegate() { printf("filler"); int abc() { return a; } printf("filler"); return &abc; } } void call(int delegate() dg) { writefln("foo: %d", dg()); } int delegate() makeDelegate1() { int x = 27; int abc() { return x; } return &abc; } int delegate() makeDelegate2() { Foo f; int abc() { return f.a; } return &abc; } int delegate() makeDelegate3() { Foo f; return &f.bar; } int delegate() makeDelegate4b(ref Foo f) { int abc() { return f.a; } printf("filler"); return &abc; } int delegate() makeDelegate4() { Foo f; printf("filler"); return makeDelegate4b(f); } void main() { auto d1 = makeDelegate1(); printf("filler"); call(d1); auto d2 = makeDelegate2(); printf("filler"); call(d2); auto d3 = makeDelegate3(); printf("filler"); call(d3); auto d4 = makeDelegate4(); printf("filler"); call(d4); Foo f; call(&f.bar); } Now the output is: fillerfoo: 27 fillerfoo: 7 fillerfoo: 4354187 fillerfillerfillerfoo: 7 foo: 7 It shows some difference still compared to your output. So are some things improved between 2.029 and 2.031? I don't know much about where and when to add "scope" to avoid creation of closures. Maybe Andrei's book will help me understand/learn better :-) Bye, bearophile
Jul 16 2009
On Thu, Jul 16, 2009 at 2:33 PM, bearophile<bearophileHUGS lycos.com> wrote= :Jens:Strange. I use 2.031 on Windows too, and I get strange values for the third and fourth items, as expected.=A0// On dmd v2.029, linux build, this... =A0 call(makedelegate1()); // ...works: 27 =A0 call(makedelegate2()); // ...works: 7 =A0 call(makedelegate3()); // ...doesn't work: 134518855 =A0 call(makedelegate4()); // ...doesn't work: 134518947 =A0 Foo f; =A0 call(&f.bar); // ...works: 7I have run your code with DMD v2.031 on Windows, and it prints: foo: 27 foo: 7 foo: 7 foo: 7 foo: 7
Jul 16 2009