Thread overview
[Issue 2939] New: lazy evaluation not invoked for lambda function
May 05, 2009
d-bugmail
May 05, 2009
d-bugmail
May 05, 2009
d-bugmail
May 05, 2009
d-bugmail
May 05, 2009
d-bugmail
May 05, 2009
d-bugmail
May 05, 2009
d-bugmail
May 05, 2009
d-bugmail
May 06, 2009
d-bugmail
May 06, 2009
d-bugmail
May 05, 2009
http://d.puremagic.com/issues/show_bug.cgi?id=2939

           Summary: lazy evaluation not invoked for lambda function
           Product: D
           Version: 2.029
          Platform: PC
        OS/Version: Windows
            Status: NEW
          Severity: normal
          Priority: P2
         Component: DMD
        AssignedTo: bugzilla@digitalmars.com
        ReportedBy: cristian@zerobugs.org


The following code illustrates how a lambda function is not evaluated when the caller is inside of a foreach block.

class X
{
    int bogus;

    int opApply(int delegate(ref X) dg)
    {
        return dg(this);
    }
}

void f(lazy void dg)
{
    dg();
}

void main()
{
    bool ok = false;
    void okay() { ok = true; }
    X x = new X;
    foreach(elem; x)
    {
        f({ok = true;}); // ASSERTION WILL FAIL FOR LAMBDA
        //f (okay); // works okay for other functions
    }
    assert(ok);
}


-- 

May 05, 2009
http://d.puremagic.com/issues/show_bug.cgi?id=2939





------- Comment #1 from jarrett.billingsley@gmail.com  2009-05-05 00:16 -------
The foreach loop is actually not important.

void f(lazy void dg)
{
        dg();
}

void main()
{
        void foo() { Stdout.formatln("o hai"); }
        f(foo);
        f({Stdout.formatln("lol wut");});
}

Only 'o hai' is printed, 'lol wut' never makes it.

In order to make it work, you have to put parens after the lambda:

f({Stdout.formatln("lol wut");}());

I'm not justifying the compiler's behavior :{ but that's the workaround I've been using.


-- 

May 05, 2009
http://d.puremagic.com/issues/show_bug.cgi?id=2939





------- Comment #2 from jarrett.billingsley@gmail.com  2009-05-05 00:17 -------
Also, this happens in D1 as well.  I'm never clear on what should be done with the bug versions in these cases..


-- 

May 05, 2009
http://d.puremagic.com/issues/show_bug.cgi?id=2939


clugdbug@yahoo.com.au changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
            Version|2.029                       |1.042




------- Comment #3 from clugdbug@yahoo.com.au  2009-05-05 00:36 -------
(In reply to comment #2)
> Also, this happens in D1 as well.  I'm never clear on what should be done with the bug versions in these cases..

It should be set to D1. Most D1 bugs also apply to D2; the converse is not
true.
Actually I think it'd be much more useful if the version identifier only had
"D1", "D2". (and maybe "D1&D2" or similar).
The mass of values are a nuisance, you have to keep updating your searches if
you want to look for only D1 bugs, for example.


-- 

May 05, 2009
http://d.puremagic.com/issues/show_bug.cgi?id=2939





------- Comment #4 from cristian@zerobugs.org  2009-05-05 02:24 -------
Here's a proposed fix: in expression.c, at line 693 (using 2.029 as a reference) add a check to see if arg is already a delegate:

    if (arg->type->ty != Tdelegate) // <---- ADD THIS CHECK
        arg = arg->toDelegate(sc, p->type);


-- 

May 05, 2009
http://d.puremagic.com/issues/show_bug.cgi?id=2939





------- Comment #5 from shro8822@vandals.uidaho.edu  2009-05-05 12:36 -------

I think this is working correctly:

take this example:

import std.stdio;
void fn(lazy int i)
{
    writef("%d\n", k);
    auto j = i();
    writef("%d\n", k);
    auto h = i();
    writef("%d\n", k);
}

int k = 0;

void main()
{
    writef("%d\n", k);
    fn(k++);
    writef("%d\n", k);

}

output:

0
0
1
2
2

What is happening in the original cases is that the 'dg();' is evaluating *to the* lambda rather than *evaluating* the lambda. And this is correct as the expression that f was called with is the lambda. (If there is a problem here is it the old one of the skipping the perens on void functions thing)

To look at it another way, dg is (almost):

delegate void(){ return delegate void(){ ok = true; } }

(it's got to play around a bit with context pointers and whatnot but that's side issue)


-- 

May 05, 2009
http://d.puremagic.com/issues/show_bug.cgi?id=2939





------- Comment #6 from jarrett.billingsley@gmail.com  2009-05-05 14:52 -------
(In reply to comment #5)

> What is happening in the original cases is that the 'dg();' is evaluating *to the* lambda rather than *evaluating* the lambda. And this is correct as the expression that f was called with is the lambda. (If there is a problem here is it the old one of the skipping the perens on void functions thing)
> 
> To look at it another way, dg is (almost):
> 
> delegate void(){ return delegate void(){ ok = true; } }

Yes, I'm pretty sure that's what's happening.  But there are two issues:

(1) It's extremely counterintuitive, easy to forget, and when you invariably get bitten by it, the compiler and runtime give no help diagnosing the problem.

(2) Why does passing a delegate reference work, but not a lambda?  They are *the same type* and you'd expect the compiler to do *the same thing* with both.


-- 

May 05, 2009
http://d.puremagic.com/issues/show_bug.cgi?id=2939





------- Comment #7 from shro8822@vandals.uidaho.edu  2009-05-05 15:16 -------
(In reply to comment #6)
> (In reply to comment #5)
> 
> > What is happening in the original cases is that the 'dg();' is evaluating *to the* lambda rather than *evaluating* the lambda. And this is correct as the expression that f was called with is the lambda. (If there is a problem here is it the old one of the skipping the perens on void functions thing)
> > 
> > To look at it another way, dg is (almost):
> > 
> > delegate void(){ return delegate void(){ ok = true; } }
> 
> Yes, I'm pretty sure that's what's happening.  But there are two issues:
> 
> (1) It's extremely counterintuitive, easy to forget, and when you invariably get bitten by it, the compiler and runtime give no help diagnosing the problem.

fair enough complaint

> 
> (2) Why does passing a delegate reference work, but not a lambda?  They are *the same type* and you'd expect the compiler to do *the same thing* with both.
> 

I can't prove it but I'd bet this is the same thing: the expression "okay" is begin converted to "okay()" via the no perens rule. so for dg on the inside you get:

delegate void(){ okay(); }


-- 

May 06, 2009
http://d.puremagic.com/issues/show_bug.cgi?id=2939





------- Comment #8 from cristian@zerobugs.org  2009-05-05 21:58 -------
I was able to test that my fix works:

            // Convert lazy argument to a delegate
            if (p->storageClass & STClazy)
            {
            if (arg->type->ty != Tdelegate) // DO NOT "DELEGATIZE" TWICE
                        arg = arg->toDelegate(sc, p->type);
            }

It is a trivial one-liner patch but this bug needs to be voted up to make it to Walter's radar!


-- 

May 06, 2009
http://d.puremagic.com/issues/show_bug.cgi?id=2939





------- Comment #9 from shro8822@vandals.uidaho.edu  2009-05-06 16:06 -------
(In reply to comment #8)
>             if (arg->type->ty != Tdelegate) // DO NOT "DELEGATIZE" TWICE

Please no!

I don't think this is the correct way to fix this as it is changing the wrong behavior. The correct solution would be to alter the 'no perens on void calls' thing, maybe just in this case.


--