Bug 2939 – lazy evaluation not invoked for lambda function
Status
RESOLVED
Resolution
INVALID
Severity
normal
Priority
P2
Component
dmd
Product
D
Version
D1 (retired)
Platform
x86
OS
Windows
Creation time
2009-05-04T23:49:00Z
Last change time
2016-08-09T21:30:47Z
Keywords
patch, wrong-code
Assigned to
bugzilla
Creator
cristian
Comments
Comment #0 by cristian — 2009-05-04T23:49:38Z
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);
}
Comment #1 by jarrett.billingsley — 2009-05-05T00:16:46Z
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.
Comment #2 by jarrett.billingsley — 2009-05-05T00:17:38Z
Also, this happens in D1 as well. I'm never clear on what should be done with the bug versions in these cases..
Comment #3 by clugdbug — 2009-05-05T00:36:00Z
(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.
Comment #4 by cristian — 2009-05-05T02:24:02Z
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);
Comment #5 by shro8822 — 2009-05-05T12:36:01Z
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)
Comment #6 by jarrett.billingsley — 2009-05-05T14:52:08Z
(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.
Comment #7 by shro8822 — 2009-05-05T15:16:18Z
(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(); }
Comment #8 by cristian — 2009-05-05T21:58:52Z
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!
Comment #9 by shro8822 — 2009-05-06T16:06:51Z
(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.
Comment #10 by ag0aep6g — 2016-08-09T21:30:47Z
(In reply to Jarrett Billingsley from comment #1)
> void f(lazy void dg)
> {
> dg();
> }
>
> void main()
> {
> void foo() { Stdout.formatln("o hai"); }
> f(foo);
`foo` is a call here. A delegate would be `&foo`. The call is deferred, because f's parameter is lazy. `dg()` in f executes the call.
> f({Stdout.formatln("lol wut");});
This passes a delegate to f, lazily of course. So `dg()` in f evaluates to the delegate. The delegate itself is never called.
> }
(In reply to Jarrett Billingsley from comment #6)
> 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.
That warrants an enhancement request, at best. If everything works as specified, then it's not a bug. Personally, I don't think the behavior here is particularly surprising.
> (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.
Not the case as explained above.
I'm closing this as INVALID. If, after seven years, anyone still thinks that this issue should be addressed, please file another issue and make it an enhancement request. Of course, if you disagree with my assessment here, feel free to reopen this bug.