www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - weird behavior returning delegate

reply Carlos Santander <csantander619 gmail.com> writes:
This code, with DMD 0.162 on Windows:

//-----------------------
T delegate (T) acc (T) (T n)
{
	return (T i) { return n += i; };
}

void foo ()
{
	auto acc1 = acc (4);
	assert (5 == acc1 (1), "5 != 5");
	assert (7 == acc1 (2), "7 != 7");

	auto acc2 = acc (10);
	assert (11 == acc2 (1), "11 != 11");
	assert (12 == acc2 (1), "12 != 12");

	assert (10 == acc1 (3), "10 != 10"); // 16
	assert (20 == acc2 (8), "20 != 20");
}

import std.stdio;

void bar ()
{
	auto acc1 = acc (4);
	writefln (" 5 == %s", acc1 (1));
	writefln (" 7 == %s", acc1 (2));

	auto acc2 = acc (10);
	writefln ("11 == %s", acc2 (1));
	writefln ("12 == %s", acc2 (1));

	writefln ("10 == %s", acc1 (3));
	writefln ("20 == %s", acc2 (8));
	writefln ();
}

void main()
{
	bar ();
	foo ();
}
//-----------------------

Produces this:

  5 == 5
  7 == 10
11 == 11
12 == 9
10 == 13
20 == 17

Error: AssertError Failure test.d(16) 10 != 10

I think the assert failing is somewhat expected (stack frame no longer 
available), but how can the previous asserts work when the values shown with 
writefln are not the ones expected, or supposedly asserting correctly?

-- 
Carlos Santander Bernal
Jul 01 2006
parent reply Sean Kelly <sean f4.ca> writes:
Carlos Santander wrote:
 This code, with DMD 0.162 on Windows:
 
 //-----------------------
 T delegate (T) acc (T) (T n)
 {
     return (T i) { return n += i; };
 }
 
 void foo ()
 {
     auto acc1 = acc (4);
     assert (5 == acc1 (1), "5 != 5");
     assert (7 == acc1 (2), "7 != 7");
 
     auto acc2 = acc (10);
     assert (11 == acc2 (1), "11 != 11");
     assert (12 == acc2 (1), "12 != 12");
 
     assert (10 == acc1 (3), "10 != 10"); // 16
     assert (20 == acc2 (8), "20 != 20");
 }
 
 import std.stdio;
 
 void bar ()
 {
     auto acc1 = acc (4);
     writefln (" 5 == %s", acc1 (1));
     writefln (" 7 == %s", acc1 (2));
 
     auto acc2 = acc (10);
     writefln ("11 == %s", acc2 (1));
     writefln ("12 == %s", acc2 (1));
 
     writefln ("10 == %s", acc1 (3));
     writefln ("20 == %s", acc2 (8));
     writefln ();
 }
 
 void main()
 {
     bar ();
     foo ();
 }
 //-----------------------
 
 Produces this:
 
  5 == 5
  7 == 10
 11 == 11
 12 == 9
 10 == 13
 20 == 17
 
 Error: AssertError Failure test.d(16) 10 != 10
 
 I think the assert failing is somewhat expected (stack frame no longer 
 available), but how can the previous asserts work when the values shown 
 with writefln are not the ones expected, or supposedly asserting correctly?

It's because you're using one function to do the asserts and another to print results, so the values returned from acc1 and acc2 may be different. I combined the two cases by storing the result in a number then performing the assert, etc, and the errors corresponded with what was displayed. Sean
Jul 01 2006
parent reply Carlos Santander <csantander619 gmail.com> writes:
Sean Kelly escribió:
 Carlos Santander wrote:
 This code, with DMD 0.162 on Windows:

 //-----------------------
 T delegate (T) acc (T) (T n)
 {
     return (T i) { return n += i; };
 }

 void foo ()
 {
     auto acc1 = acc (4);
     assert (5 == acc1 (1), "5 != 5");
     assert (7 == acc1 (2), "7 != 7");

     auto acc2 = acc (10);
     assert (11 == acc2 (1), "11 != 11");
     assert (12 == acc2 (1), "12 != 12");

     assert (10 == acc1 (3), "10 != 10"); // 16
     assert (20 == acc2 (8), "20 != 20");
 }

 import std.stdio;

 void bar ()
 {
     auto acc1 = acc (4);
     writefln (" 5 == %s", acc1 (1));
     writefln (" 7 == %s", acc1 (2));

     auto acc2 = acc (10);
     writefln ("11 == %s", acc2 (1));
     writefln ("12 == %s", acc2 (1));

     writefln ("10 == %s", acc1 (3));
     writefln ("20 == %s", acc2 (8));
     writefln ();
 }

 void main()
 {
     bar ();
     foo ();
 }
 //-----------------------

 Produces this:

  5 == 5
  7 == 10
 11 == 11
 12 == 9
 10 == 13
 20 == 17

 Error: AssertError Failure test.d(16) 10 != 10

 I think the assert failing is somewhat expected (stack frame no longer 
 available), but how can the previous asserts work when the values 
 shown with writefln are not the ones expected, or supposedly asserting 
 correctly?

It's because you're using one function to do the asserts and another to print results, so the values returned from acc1 and acc2 may be different. I combined the two cases by storing the result in a number then performing the assert, etc, and the errors corresponded with what was displayed. Sean

I did the same too, but that's what I don't understand: what are really the correct values, the ones that are printed or the ones that pass the asserts? Also, I don't think putting them in one, two or more functions really matters. I would expect the previous acc1 and acc2 to be gone when the function exits. Anyway, bottom line is I think I'm hoping for too much. Unless something like Burton's proposed "new delegate" syntax is put into the language, this wouldn't really work properly. -- Carlos Santander Bernal
Jul 01 2006
parent reply Sean Kelly <sean f4.ca> writes:
Carlos Santander wrote:
 Sean Kelly escribió:
 It's because you're using one function to do the asserts and another 
 to print results, so the values returned from acc1 and acc2 may be 
 different.  I combined the two cases by storing the result in a number 
 then performing the assert, etc, and the errors corresponded with what 
 was displayed.

I did the same too, but that's what I don't understand: what are really the correct values, the ones that are printed or the ones that pass the asserts?

When you enter the realm of undefined behavior, anything goes. In this case I think the residual stack data is just a bit different in foo() and bar(), possibly because bar() is called before foo() or simply as an artifact of different code generation, so the calls that break are different in each case.
 Also, I don't think putting them in one, two or more functions really 
 matters. I would expect the previous acc1 and acc2 to be gone when the 
 function exits.

Yes but the delegates are referencing invalid stack locations, and so there's no guarantee that the data will be the same even if the basic sequence of calls is similar for both foo() and bar().
 Anyway, bottom line is I think I'm hoping for too much. Unless something 
 like Burton's proposed "new delegate" syntax is put into the language, 
 this wouldn't really work properly.

Yeah. I think it might work if acc() accepted 'n' as 'inout', but doing so still seems a bad idea as the delegate's stack context is technically invalid at the point where it's called by foo() and bar(). Sean
Jul 01 2006
parent reply Carlos Santander <csantander619 gmail.com> writes:
Sean Kelly escribió:
 Carlos Santander wrote:
 Sean Kelly escribió:
 It's because you're using one function to do the asserts and another 
 to print results, so the values returned from acc1 and acc2 may be 
 different.  I combined the two cases by storing the result in a 
 number then performing the assert, etc, and the errors corresponded 
 with what was displayed.

I did the same too, but that's what I don't understand: what are really the correct values, the ones that are printed or the ones that pass the asserts?

When you enter the realm of undefined behavior, anything goes. In this case I think the residual stack data is just a bit different in foo() and bar(), possibly because bar() is called before foo() or simply as an artifact of different code generation, so the calls that break are different in each case.
 Also, I don't think putting them in one, two or more functions really 
 matters. I would expect the previous acc1 and acc2 to be gone when the 
 function exits.

Yes but the delegates are referencing invalid stack locations, and so there's no guarantee that the data will be the same even if the basic sequence of calls is similar for both foo() and bar().
 Anyway, bottom line is I think I'm hoping for too much. Unless 
 something like Burton's proposed "new delegate" syntax is put into the 
 language, this wouldn't really work properly.

Yeah. I think it might work if acc() accepted 'n' as 'inout', but doing so still seems a bad idea as the delegate's stack context is technically invalid at the point where it's called by foo() and bar(). Sean

Thanks for the answers. I was hoping D could be added to http://www.paulgraham.com/accgen.html only with three lines instead of something like that horrible C++ code. -- Carlos Santander Bernal
Jul 02 2006
parent reply Tom S <h3r3tic remove.mat.uni.torun.pl> writes:
Carlos Santander wrote:
 Thanks for the answers. I was hoping D could be added to 
 http://www.paulgraham.com/accgen.html only with three lines instead of 
 something like that horrible C++ code.

It's not very bad in D: T delegate(T) acc(T)(T i) { class Foo { T x; this() { x = i; } T func(T a) { return x += a; } } return &(new Foo).func; } or T delegate(T) acc(T)(T i) { auto res = new class Object { T x; T func(T a) { return x += a; } }; res.x = i; return &res.func; } -- Tomasz Stachowiak /+ a.k.a. h3r3tic +/
Jul 02 2006
next sibling parent reply Chris Nicholson-Sauls <ibisbasenji gmail.com> writes:
Tom S wrote:
 Carlos Santander wrote:
 
 Thanks for the answers. I was hoping D could be added to 
 http://www.paulgraham.com/accgen.html only with three lines instead of 
 something like that horrible C++ code.

It's not very bad in D: T delegate(T) acc(T)(T i) { class Foo { T x; this() { x = i; } T func(T a) { return x += a; } } return &(new Foo).func; } or T delegate(T) acc(T)(T i) { auto res = new class Object { T x; T func(T a) { return x += a; } }; res.x = i; return &res.func; }

Or even this: # T delegate (T) foo (T) (T n) { # with ( new class Object { private T x; public T acc (T i) { return x += i; } } ) { # x = n; # return &acc; # } # } If in delegate literals we could declare static variables initialized from the local frame, then it could even have simply been this: # T delegate (T) foo (T) (T n) { # return (T i) { # static T x = n ; # return x += i ; # }; # } Alas. -- Chris Nicholson-Sauls
Jul 02 2006
next sibling parent reply Bruno Medeiros <brunodomedeirosATgmail SPAM.com> writes:
Chris Nicholson-Sauls wrote:
 
 If in delegate literals we could declare static variables initialized 
 from the local frame, then it could even have simply been this:
 
 # T delegate (T) foo (T) (T n) {
 #   return (T i) {
 #     static T x  = n ;
 #     return   x += i ;
 #   };
 # }
 
 Alas.
 
 -- Chris Nicholson-Sauls

Nope, even if the static variable were to be initialized at delegate literal evaluation time, instead of delegate run time, it wouldn't work as there is only one delegate "body instance", so each accumulator would share the same static var. -- Bruno Medeiros - CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Jul 03 2006
parent Chris Nicholson-Sauls <ibisbasenji gmail.com> writes:
Bruno Medeiros wrote:
 Chris Nicholson-Sauls wrote:
 
 If in delegate literals we could declare static variables initialized 
 from the local frame, then it could even have simply been this:

 # T delegate (T) foo (T) (T n) {
 #   return (T i) {
 #     static T x  = n ;
 #     return   x += i ;
 #   };
 # }

 Alas.

 -- Chris Nicholson-Sauls

Nope, even if the static variable were to be initialized at delegate literal evaluation time, instead of delegate run time, it wouldn't work as there is only one delegate "body instance", so each accumulator would share the same static var.

True enough. If 'n' were part of the template's parameters then it would be a bit closer, as then there would be a seperate function for each starting value... but then you couldn't use 'foo(N)' more than once for any given N. I guess the only way to pull this off is going to be with a class, like the other three examples. Seems like a shame, though. -- Chris Nicholson-Sauls
Jul 03 2006
prev sibling parent reply Tom S <h3r3tic remove.mat.uni.torun.pl> writes:
Or for the extremists...


T delegate(T a) acc(T)(T i){
	struct Foo {
		T a;
		T func(T x) { return a += x; }
	}
	return &(cast(Foo*)((&i)[0..1]).dup).func;
}

or...

T delegate(T a) acc(T)(T i){
	struct Foo {
		T a;
		T func(T x) { return a += x; }
	}
	return &(cast(Foo*)((new T[1])[] = i)).func;
}



-- 
Tomasz Stachowiak  /+ a.k.a. h3r3tic +/
Jul 03 2006
parent reply John Reimer <terminal.node gmail.com> writes:
Tom S wrote:
 Or for the extremists...
 
 
 T delegate(T a) acc(T)(T i){
     struct Foo {
         T a;
         T func(T x) { return a += x; }
     }
     return &(cast(Foo*)((&i)[0..1]).dup).func;
 }
 
 or...
 
 T delegate(T a) acc(T)(T i){
     struct Foo {
         T a;
         T func(T x) { return a += x; }
     }
     return &(cast(Foo*)((new T[1])[] = i)).func;
 }
 
 
 

Bad, Tom. You've been playing with templates again, haven't you? ;) -JJR
Jul 03 2006
parent Tom S <h3r3tic remove.mat.uni.torun.pl> writes:
John Reimer wrote:
 Bad, Tom.  You've been playing with templates again, haven't you? ;)

But ... but ... um ... I'm sorry :( /* crosses fingers */ I promise I'll be a good boy ;) -- Tomasz Stachowiak /+ a.k.a. h3r3tic +/
Jul 03 2006
prev sibling parent Bruno Medeiros <brunodomedeirosATgmail SPAM.com> writes:
Tom S wrote:
 Carlos Santander wrote:
 Thanks for the answers. I was hoping D could be added to 
 http://www.paulgraham.com/accgen.html only with three lines instead of 
 something like that horrible C++ code.

It's not very bad in D: T delegate(T) acc(T)(T i) { class Foo { T x; this() { x = i; } T func(T a) { return x += a; } } return &(new Foo).func; } or T delegate(T) acc(T)(T i) { auto res = new class Object { T x; T func(T a) { return x += a; } }; res.x = i; return &res.func; }

Also, if something like Markus Dangl partial application were available in the library, it could be this as well: T delegate(T) acc(T)(T i) { return &AdvancedDelegate( (T num, T inc) { return num + inc; } ) (i).Eval; } :) -- Bruno Medeiros - CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Jul 03 2006