April 22, 2020
On 4/22/20 12:50 PM, Petar Kirov [ZombineDev] wrote:
> On Wednesday, 22 April 2020 at 15:15:24 UTC, Steven Schveighoffer wrote:
>>
>> Really the only use of lazy I use is in enforce.
> 
> I'd say that object.d has some pretty good use cases in addition to enforce:
> 
> inout(V) get(K, V)(inout(V[K]) aa, K key, lazy inout(V) defaultValue):
> https://github.com/dlang/druntime/blob/v2.091.0/src/object.d#L2576
> 
> ref V require(K, V)(ref V[K] aa, K key, lazy V value = V.init):
> https://github.com/dlang/druntime/blob/v2.091.0/src/object.d#L2605
> 
> 
> 

Right, and I still think doing mutations in those expressions would be confusing.

-Steve
April 22, 2020
On Wednesday, 22 April 2020 at 16:53:09 UTC, Petar Kirov [ZombineDev] wrote:
> If this DIP is accepted, `lazy` could be deprecated, as it could be fully replaced by scope delegates, which are nothrow @nogc -betterC compatible.

We don't need the DIP for that. It would just allow not having to touch the call sites (just the lazy *params*), but as said, I think the arguments should be forced to be converted to regular delegates too, for legibility and less confusion.
April 22, 2020
On Wednesday, 22 April 2020 at 17:48:35 UTC, kinke wrote:
> On Wednesday, 22 April 2020 at 16:53:09 UTC, Petar Kirov [ZombineDev] wrote:
>> If this DIP is accepted, `lazy` could be deprecated, as it could be fully replaced by scope delegates, which are nothrow @nogc -betterC compatible.
>
> We don't need the DIP for that.

We need it in order to avoid API breaking changes. If we deprecate `lazy`, without this DIP this code will break:

  const result = cache.require(key, calculateValue(key));

> It would just allow not having to touch the call sites (just the lazy *params*), but as said, I think the arguments should be forced to be converted to regular delegates too, for legibility and less confusion.

Reasonable people may disagree. Just like C programmers like the explicitness of passing a pointer to a local variable, while `T&` is being touted as one of the big improvements of C++, compared to C.
April 22, 2020
On Wednesday, 22 April 2020 at 19:08:08 UTC, Petar Kirov [ZombineDev] wrote:
> We need it in order to avoid API breaking changes. If we deprecate `lazy`, without this DIP this code will break:
>
>   const result = cache.require(key, calculateValue(key));

I thought these cases could later be simplified by overloading these few functions, taking either a value directly or a factory delegate. But after scanning for lazy params in Phobos, it looks like its use is reasonably restricted to a few places where the shortcut syntax is indeed nice (enforce, assertThrown, logging...).

But I still think this DIP would be a step in the wrong direction; instead, I'd prefer `lazy` to keep designating params where arguments are eligible for this shortcut syntax / implicit conversion of expressions to delegates. The problems with lazy ('being an oddity means it is hard to reason about, especially with the proliferation of parameter attributes') might be solved by transitioning to requiring a delegate type for lazy parameters, e.g.:

void write(string);

alias Factory(T) = scope T delegate();
void log(lazy Factory!bool condition, lazy Factory!string msg)
{
    if (condition())
        write(msg());
}

void foo(int p)
{
    log(true, "bla");
    log(() => ++p, "blub"); // enable optional delegate syntax too
}

lazy is used very sparingly in druntime/Phobos, and AFAIK, nobody has requested the proposed implicit conversion for other expressions so far. So instead of trying out something completely new and opening the door for abuse and confusion, I think we should rather focus on the few sprinkled usages of lazy params.
April 23, 2020
Followup discussion to Walter's response to my feedback:

>> int delegate() = 3;
>>
>> doesn't use any globals. Why shouldn't this work?
>>
>> int function() = 3;
> 
> Because it seems kinda pointless to support function pointers only with constants.
> 
> While it is not impossible to support function pointers with this, I can't think of a compelling use case. 

Walter, you literally are using int delegate() = 3 as a use case in the DIP. How is a delegate that returns a constant more useful (or different) than a function pointer that returns a constant?

The statement "the expression can only consist of globals" is just factually incorrect. It can consist of globals, new memory, literals, enums, thread local data, the result of some other function call with those types of parameters.

I tend to think that it doesn't make a whole lot of sense to exclude function pointers in this DIP, but even if you disagree, the text fails to convey a valid rationale of why they shouldn't be included.

-Steve
April 23, 2020
On Wednesday, 22 April 2020 at 11:56:44 UTC, kinke wrote:
> I don't think this is a good idea. One of the problems with `lazy` IMO is that one needs good IDE support to somehow highlight that the argument expression isn't evaluated before the call, and might not be evaluated at all:
>
> // in some other module:
> void foo(lazy int i) {}
>
> void main()
> {
>     int i = 1;
>     foo(++i);
>     assert(i == 1); // what? not incremented above? ah yeah, lazy and not evaluated at all by foo
> }
>
> The proposal would extend that problem to regular delegate params too. So I find the explicit version (`() => ++i`) better to make that clear. I don't think the 'overhead' of putting an additional `() =>` in front of the expression justifies introducing the proposed implicit convertibility.

I agree with this. Implicit conversion has always been argued against in D - why is it ok now? And this is not the good kind of implicit conversion where the user explicitly asks for it, but the bad kind where the language decides it should happen and the user doesn't know it can even happen.

Thinking about it more now feels like the real rationale for the DIP is "in order to deprecate lazy, we need an alternative" ... ???

April 23, 2020
On Thursday, 23 April 2020 at 09:33:35 UTC, aliak wrote:
> On Wednesday, 22 April 2020 at 11:56:44 UTC, kinke wrote:
>> I don't think this is a good idea. One of the problems with `lazy` IMO is that one needs good IDE support to somehow highlight that the argument expression isn't evaluated before the call, and might not be evaluated at all:
>>
>> // in some other module:
>> void foo(lazy int i) {}
>>
>> void main()
>> {
>>     int i = 1;
>>     foo(++i);
>>     assert(i == 1); // what? not incremented above? ah yeah, lazy and not evaluated at all by foo
>> }
>>
>> The proposal would extend that problem to regular delegate params too. So I find the explicit version (`() => ++i`) better to make that clear. I don't think the 'overhead' of putting an additional `() =>` in front of the expression justifies introducing the proposed implicit convertibility.
>
> I agree with this. Implicit conversion has always been argued against in D - why is it ok now? And this is not the good kind of implicit conversion where the user explicitly asks for it, but the bad kind where the language decides it should happen and the user doesn't know it can even happen.
>
> Thinking about it more now feels like the real rationale for the DIP is "in order to deprecate lazy, we need an alternative" ... ???

I always thought that lazy should also be marked at the call site as it is a very different semantic on the caller and on the callee. As it is now, it is true that the example above is unfortunate and a real source of unpleasant surprizes.


void foo(lazy int i) {}

 void main()
 {
     int i = 1;
     foo(lazy ++i);
     assert(i == 1); // No surprize here, the required annotation tells it.
 }

April 23, 2020
On Thursday, 23 April 2020 at 11:13:34 UTC, Patrick Schluter wrote:
>
> I always thought that lazy should also be marked at the call site as it is a very different semantic on the caller and on the callee. As it is now, it is true that the example above is unfortunate and a real source of unpleasant surprizes.
+1

Step 1: lazy must be used at the call site
Step 2: deprecate lazy
Step 3: remove this terrible, unreadable idea that was done hastily years ago with no vote whatsoever
April 23, 2020
On Thursday, 23 April 2020 at 13:20:08 UTC, Guillaume Piolat wrote:
> On Thursday, 23 April 2020 at 11:13:34 UTC, Patrick Schluter wrote:
>>
>> I always thought that lazy should also be marked at the call site as it is a very different semantic on the caller and on the callee. As it is now, it is true that the example above is unfortunate and a real source of unpleasant surprizes.
> +1
>
> Step 1: lazy must be used at the call site

That would play havoc with generic code and would significantly increase the complexity of standard phobos functions like map, filter, etc.

April 23, 2020
On 4/23/20 7:13 AM, Patrick Schluter wrote:
> On Thursday, 23 April 2020 at 09:33:35 UTC, aliak wrote:
>> On Wednesday, 22 April 2020 at 11:56:44 UTC, kinke wrote:
>>> I don't think this is a good idea. One of the problems with `lazy` IMO is that one needs good IDE support to somehow highlight that the argument expression isn't evaluated before the call, and might not be evaluated at all:
>>>
>>> // in some other module:
>>> void foo(lazy int i) {}
>>>
>>> void main()
>>> {
>>>     int i = 1;
>>>     foo(++i);
>>>     assert(i == 1); // what? not incremented above? ah yeah, lazy and not evaluated at all by foo
>>> }
>>>
>>> The proposal would extend that problem to regular delegate params too. So I find the explicit version (`() => ++i`) better to make that clear. I don't think the 'overhead' of putting an additional `() =>` in front of the expression justifies introducing the proposed implicit convertibility.
>>
>> I agree with this. Implicit conversion has always been argued against in D - why is it ok now? And this is not the good kind of implicit conversion where the user explicitly asks for it, but the bad kind where the language decides it should happen and the user doesn't know it can even happen.
>>
>> Thinking about it more now feels like the real rationale for the DIP is "in order to deprecate lazy, we need an alternative" ... ???
> 
> I always thought that lazy should also be marked at the call site as it is a very different semantic on the caller and on the callee. As it is now, it is true that the example above is unfortunate and a real source of unpleasant surprizes.
> 
> 
> void foo(lazy int i) {}
> 
>   void main()
>   {
>       int i = 1;
>       foo(lazy ++i);
>       assert(i == 1); // No surprize here, the required annotation tells it.
>   }
> 

If lazy binds to delegates then this just becomes

foo(() => ++i);

And then we can require lazy to require a delegate at the call site, and then get rid of lazy.

-Steve