Thread overview
Assigning value to a lazy parameter in a function call
May 11, 2012
Vidar Wahlberg
May 11, 2012
Chris Cain
May 11, 2012
Vidar Wahlberg
May 11, 2012
Chris Cain
May 11, 2012
Vidar Wahlberg
May 11, 2012
Chris Cain
May 11, 2012
Wasn't easy to find a short good description of the issue in the subject, but here's some code to illustrate my "concern":
---
import std.stdio;
void log(T...)(lazy string message, lazy T t) {
        debug writefln(message, t);
}
void main() {
        int a = 42;
        writefln("The meaning of life is %s", a);
        log("Incrementing the meaning of life: %s", ++a);
        writefln("The meaning of life is now %s", a);
}
---

I often call functions where one of the parameters may be an integer which i post/pre increment/decrement. However, that can be quite risky if the parameter is defined as "lazy" as shown above.
The value of "a" above after calling "log" depends on whether you've compiled with "-debug" or not, this may not be very obvious and may take some by surprise.

Perhaps the compiler should print out a warning when you're assigning a value to a lazy parameter in a function call?
May 11, 2012
On Friday, 11 May 2012 at 20:45:53 UTC, Vidar Wahlberg wrote:
> I often call functions where one of the parameters may be an integer which i post/pre increment/decrement. However, that can be quite risky if the parameter is defined as "lazy" as shown above.
> The value of "a" above after calling "log" depends on whether you've compiled with "-debug" or not, this may not be very obvious and may take some by surprise.
>
> Perhaps the compiler should print out a warning when you're assigning a value to a lazy parameter in a function call?

The entire point of a lazy parameter is to not be
calculated/processed until it's actually necessary. This is
normal behavior for lazy. Most actual use cases for lazy would be made impractical if the compiler bombarded the programmer with warnings.
May 11, 2012
On 2012-05-11 23:03, Chris Cain wrote:
> On Friday, 11 May 2012 at 20:45:53 UTC, Vidar Wahlberg wrote:
>> Perhaps the compiler should print out a warning when you're assigning
>> a value to a lazy parameter in a function call?
>
> The entire point of a lazy parameter is to not be
> calculated/processed until it's actually necessary. This is
> normal behavior for lazy. Most actual use cases for lazy would be made
> impractical if the compiler bombarded the programmer with warnings.

I'm not suggesting that the compiler should print a warning if you're doing a calculation in the function call, I'm suggesting it should give you a warning if you're assigning the result of the calculation to a variable in the function call.
In other words, «log("%s", a + 1);» would give no warning, while «log("%s", ++a);» and «log("%s", (a = a + 1));» would.

(Sorry if this is a duplicate, got an error upon sending, it appears like the message never was sent)
May 11, 2012
On Friday, 11 May 2012 at 21:39:57 UTC, Vidar Wahlberg wrote:
> I'm not suggesting that the compiler should print a warning if you're doing a calculation in the function call, I'm suggesting it should give you a warning if you're assigning the result of the calculation to a variable in the function call.
> In other words, «log("%s", a + 1);» would give no warning, while «log("%s", ++a);» and «log("%s", (a = a + 1));» would.

The thing is, ++a != (a = a + 1). And even if it were, it's perfectly legitimate for a lazy parameter to change the state of things outside the scope of the function. Really, lazy is just syntactic sugar for a delegate.

void log(lazy string message, lazy int t)
is just the same as
void log(lazy string message, int delegate() t)

the latter must be called like

log("Incrementing the meaning of life: %s", (){return ++a;});
or
log("Incrementing the meaning of life: %s", () => ++a);

while the former can appear like:
log("Incrementing the meaning of life: %s", ++a);

In this case, I'd say that the "correct" thing to do is not have the t parameter be a lazy parameter. Generally, lazy is better used for things like the enforce provided by std.exception ... it looks something like this (I haven't seen it before, so this is an approximation based on my observations):

void enforce(bool condition, lazy string msg) {
   if(condition) throw new EnforceException(msg);
}

enforce(someCondition, "Well, it looks like " ~ obj.getNameFromDatabase() ~ " is in an invalid state ... here's a helpful representation: " ~ obj.expensiveFunction());

In this case, lazy is used to prevent the expensive calls to get the name from a database and computing a hash function unless it's absolutely necessary. And obj's state might very well be changed when getNameFromDatabase is called or when expensiveFunction is run.

> (Sorry if this is a duplicate, got an error upon sending, it appears like the message never was sent)

It's okay. It's a known bug right now due to the mass influx of users. It happens to all of us :)
May 11, 2012
On 2012-05-12 00:27, Chris Cain wrote:
> [...]

Thank you for the detailed answer.
I still suspect this can fool some people, and (in my ignorance) I couldn't (and still can't, to be honest) really see when you would want to assign a variable in a lazy parameter, I would expect that to be far more often an error from the coder rather than intended behavior. I'm not here to argue though, I just wanted to express my thoughts on the issue :)

> It's okay. It's a known bug right now due to the mass influx of users.

Which is good, the language is really interesting, I hope more people will look into it.
May 11, 2012
On Friday, 11 May 2012 at 23:07:31 UTC, Vidar Wahlberg wrote:
> Thank you for the detailed answer.
> I still suspect this can fool some people, and (in my ignorance) I couldn't (and still can't, to be honest) really see when you would want to assign a variable in a lazy parameter, I would expect that to be far more often an error from the coder rather than intended behavior. I'm not here to argue though, I just wanted to express my thoughts on the issue :)

No problem. I hope it's become a bit more clear what lazy is for.

++a isn't assigning a. It's causing a to mutate in place (which, is just changing state). And lazy parameters are all about holding the change of state off until it's necessary.

The biggest "gotcha" about lazy parameters isn't going to be that, though... it'll be something like this:

-=-=-
void funct(lazy int x) {
    writeln(x, " ", x);
}

void main() {
    int a = 0;
    funct(++a); // prints 1, 2
    // a is now 2, even though there's only one ++a
}
-=-=-

But keeping in mind that lazy is just convenient syntax sugar for a delegate function, the reason why this behaves like this is clearer.