www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.bugs - [Issue 2939] New: lazy evaluation not invoked for lambda function

reply d-bugmail puremagic.com writes:
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 04 2009
next sibling parent d-bugmail puremagic.com writes:
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 04 2009
prev sibling next sibling parent d-bugmail puremagic.com writes:
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 04 2009
prev sibling next sibling parent d-bugmail puremagic.com writes:
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 04 2009
prev sibling next sibling parent d-bugmail puremagic.com writes:
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
prev sibling next sibling parent d-bugmail puremagic.com writes:
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
prev sibling next sibling parent d-bugmail puremagic.com writes:
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
prev sibling next sibling parent d-bugmail puremagic.com writes:
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 05 2009
prev sibling next sibling parent d-bugmail puremagic.com writes:
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 05 2009
prev sibling parent d-bugmail puremagic.com writes:
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. --
May 06 2009