November 22, 2020
In the Feedback thread, Walter wrote:

> All this [the proposed changes] does is replace the 'lazy' with the delegate syntax. D already uses this for variadic lazy arguments, and nothing has come up bad about it.

Have you ever tried to use attributes on them? It's a mess. The expressions converted to delegates are not at all equivalent to literal delegates when attributes are at play.

int fun(int delegate() pure [] xs...) pure
{
    foreach (x; xs)
    if (auto value = x())
        return value;
    return 0;
}

void impureContext()
{
    import std.stdio : write;
    int impureFactory() { write(""); return 0; }

    fun(impureFactory());        // passes, but why??
    fun(&impureFactory);         // fails, no surprise
    fun(() => impureFactory());  // fails, no surprise
}

void pureContext() pure
{
    int pureFactory() pure { return 0; }

    fun(pureFactory());       // passes, no surprise
    fun(&pureFactory);        // passes, no surprise
    fun(() => pureFactory()); // passes, no surprise
}

The DIP should make clear, that "simply" lowering expressions to delegates isn't what's going on.
November 22, 2020
On 22.11.20 10:18, Walter Bright wrote:
> On 11/20/2020 12:06 AM, Manu wrote:
>> So, the thing that makes me nervous about this DIP, is that it may substantially proliferate allocated closures.
>> I would want to have high confidence that people exclusively use `scope` delegates, such that the closure can (will?/must?) be stack allocated.
>>
>> In my experience, in the rare instance that I want to use a lazy parameter in the sorts of ways that might be affected by this DIP, it's usually in support of a fairly small optimisation, or a modest convenience in the API.
>> Relative to that optimisation, if there's a risk of introducing a closure allocation, that would vastly out-weight the advantage by my judgement, and it's very easy for such allocations to go unnoticed. It's completely invisible.
>>
>> I would appeal that this DIP be changed such that auto-delegates must be `scope`, and non-scope delegates should require the user to specify a non-scope delegate argument using typical delegate syntax, so that you can plainly see it.
>> Without requiring `scope` delegates, the risk of hidden allocations is extremely high, and it's almost certainly NOT what anybody would ever want from a lazy argument, which is virtually always an INPUT argument, and shouldn't be retained past the life of the call.
> 
> Hmm, this is a good point I never thought of. Well put!

There's plenty of tools to prevent implicit allocations and if the delegate is dropped after the function call, it will be `scope` anyway. The benefit of allowing a closure allocation is that it becomes possible to implement actual lazy evaluation.
November 23, 2020
On Mon, Nov 23, 2020 at 7:46 AM Timon Gehr via Digitalmars-d < digitalmars-d@puremagic.com> wrote:

> On 22.11.20 10:18, Walter Bright wrote:
> > On 11/20/2020 12:06 AM, Manu wrote:
> >> So, the thing that makes me nervous about this DIP, is that it may
> >> substantially proliferate allocated closures.
> >> I would want to have high confidence that people exclusively use
> >> `scope` delegates, such that the closure can (will?/must?) be stack
> >> allocated.
> >>
> >> In my experience, in the rare instance that I want to use a lazy
> >> parameter in the sorts of ways that might be affected by this DIP,
> >> it's usually in support of a fairly small optimisation, or a modest
> >> convenience in the API.
> >> Relative to that optimisation, if there's a risk of introducing a
> >> closure allocation, that would vastly out-weight the advantage by my
> >> judgement, and it's very easy for such allocations to go unnoticed.
> >> It's completely invisible.
> >>
> >> I would appeal that this DIP be changed such that auto-delegates must
> >> be `scope`, and non-scope delegates should require the user to specify
> >> a non-scope delegate argument using typical delegate syntax, so that
> >> you can plainly see it.
> >> Without requiring `scope` delegates, the risk of hidden allocations is
> >> extremely high, and it's almost certainly NOT what anybody would ever
> >> want from a lazy argument, which is virtually always an INPUT
> >> argument, and shouldn't be retained past the life of the call.
> >
> > Hmm, this is a good point I never thought of. Well put!
>
> There's plenty of tools to prevent implicit allocations and if the delegate is dropped after the function call, it will be `scope` anyway. The benefit of allowing a closure allocation is that it becomes possible to implement actual lazy evaluation.
>

You can just write `() => expr` in that case, then the allocation is
visible. I think the risk of hidden allocated closures is substantial and
shouldn't be taken lightly, and I don't see evidence that deferred lazy
functions is a popular enough pattern to justify accepting this risk.
This is a restriction that can be trivially lifted in the future if the
pattern you describe is prolific and my suggestion is proven to be
cumbersome, but it's virtually impossible to add the restriction in the
future once the cat is already out of the bag.
I am _deeply_ concerned by a core language syntax that may invisibly result
in random allocations at any function callsite. It's also the sort of thing
that would be extremely off-putting to tech-directors doing
technology/language assessments in my industry; it's at the level where it
could tip someone's judgement.

Please consider a conservative approach, and lift the restriction in the future if there's demonstrated advantage.


November 23, 2020
On Monday, 23 November 2020 at 08:01:00 UTC, Manu wrote:
> This is a restriction that can be trivially lifted in the future if the
> pattern you describe is prolific and my suggestion is proven to be
> cumbersome, but it's virtually impossible to add the restriction in the
> future once the cat is already out of the bag.
> I am _deeply_ concerned by a core  language syntax that may invisibly result
> in random allocations at any function callsite.

+1, absolutely. In a systems language the caller should always be aware whether the function scope is heap-allocated or not. This is about performance, predictability and easier code review. (It's not just for those wishing to minimise use of the GC).
November 23, 2020
On 23.11.20 09:01, Manu wrote:
> ...
>      >> Without requiring `scope` delegates, the risk of hidden
>     allocations is
>      >> extremely high, and it's almost certainly NOT what anybody would
>     ever
>      >> want from a lazy argument, which is virtually always an INPUT
>      >> argument, and shouldn't be retained past the life of the call.
>      >
>      > Hmm, this is a good point I never thought of. Well put!
> 
>     There's plenty of tools to prevent implicit allocations and if the
>     delegate is dropped after the function call, it will be `scope` anyway.
>     The benefit of allowing a closure allocation is that it becomes
>     possible
>     to implement actual lazy evaluation.
> 
> 
> You can just write `() => expr` in that case, then the allocation is visible. I think the risk of hidden allocated closures is substantial and shouldn't be taken lightly, and I don't see evidence that deferred lazy functions is a popular enough pattern to justify accepting this risk.

I am not sure this is even officially supported right now. Taking the address of a lazy parameter is not @safe. (This might be an oversight, though I'm pretty sure the documentation used to state that lazy implies scope.)

> This is a restriction that can be trivially lifted in the future if the pattern you describe is prolific and my suggestion is proven to be cumbersome, but it's virtually impossible to add the restriction in the future once the cat is already out of the bag.
> I am _deeply_ concerned by a core language syntax that may invisibly result in random allocations at any function callsite.

It really does not matter much whether the allocation is at the callsite or at the beginning of the function itself. If it's at the call site, at least you can tell from the function signature...

> It's also the sort of thing that would be extremely off-putting to tech-directors doing technology/language assessments in my industry; it's at the level where it could tip someone's judgement.
> ...

We have @nogc. What's the point of even supporting @nogc if this kind of rhetoric continues anyway? I'd rather have a language that is useful instead of one that appeals to people with bad decision making.

> Please consider a conservative approach, and lift the restriction in the future if there's demonstrated advantage.

It's not up to me.
November 23, 2020
On 23.11.20 10:00, Nick Treleaven wrote:
> On Monday, 23 November 2020 at 08:01:00 UTC, Manu wrote:
>> This is a restriction that can be trivially lifted in the future if the
>> pattern you describe is prolific and my suggestion is proven to be
>> cumbersome, but it's virtually impossible to add the restriction in the
>> future once the cat is already out of the bag.
>> I am _deeply_ concerned by a coreĀ  language syntax that may invisibly result
>> in random allocations at any function callsite.
> 
> +1, absolutely. In a systems language the caller should always be aware whether the function scope is heap-allocated or not. This is about performance, predictability and easier code review. (It's not just for those wishing to minimise use of the GC).

No, sorry, that's exactly just who it is for. The ability to write code at the appropriate level of abstraction for the given use case is a strength of D.
November 23, 2020
On Monday, 23 November 2020 at 12:55:45 UTC, Timon Gehr wrote:
> If it's at the call site, at least you can tell from the function signature...

If you and all reviewers correctly detect the right overload. What if the programmer doesn't check the signature every time they use that function?

> We have @nogc. What's the point of even supporting @nogc if this kind of rhetoric continues anyway?

You may still want some GC allocations, but not want a closure allocated. Or you simply want allocations to be obvious, because they can be slow.

> I'd rather have a language that is useful instead of one that appeals to people with bad decision making.

How does having to write `() => ` for each argument flip a language from being useful to not useful?
November 24, 2020
On 23.11.20 17:00, Nick Treleaven wrote:
> On Monday, 23 November 2020 at 12:55:45 UTC, Timon Gehr wrote:
>> If it's at the call site, at least you can tell from the function signature...
> 
> If you and all reviewers correctly detect the right overload. What if the programmer doesn't check the signature every time they use that function?
> ...

What if they don't check if the function allocates?

>> We have @nogc. What's the point of even supporting @nogc if this kind of rhetoric continues anyway?
> 
> You may still want some GC allocations, but not want a closure allocated. Or you simply want allocations to be obvious, because they can be slow.
> ...

Allocations can hide behind function calls anyway.

>> I'd rather have a language that is useful instead of one that appeals to people with bad decision making.
> 
> How does having to write `() => ` for each argument flip a language from being useful to not useful?

It's a continuum... D has enough weird non-orthogonal restrictions as-is.
November 24, 2020
On Friday, 20 November 2020 at 07:29:21 UTC, Mike Parker wrote:
> This is the discussion thread for the Final Review of DIP 1033, "Implicit Conversion of Expressions to Delegates":
>
> https://github.com/dlang/DIPs/blob/8e56fc593ece5c74f18b8eb68c3f9dcedf2396a7/DIPs/DIP1033.md
>

Does DIP1033, or rather, a post-DIP1033 language, offer any hope for https://issues.dlang.org/show_bug.cgi?id=21420 ? Tl;dr: the default parameter to Nullable.get() cannot be lazy because if it were lazy, Nullable.get() could neither infer nor carry any sort of nothrow or @safe annotation, making it unusable from @safe/nothrow code.

Could DIP1033 offer something like ...

inout(T) get(DG : inout(T) delegate())(DG fallback) pure { ... }

Where DG would carry callsite inference about nothrow and @safe ness of the converted expression?
November 24, 2020
On Tuesday, 24 November 2020 at 09:47:33 UTC, FeepingCreature wrote:
>
> Could DIP1033 offer something like ...
>
> inout(T) get(DG : inout(T) delegate())(DG fallback) pure { ... }
>
> Where DG would carry callsite inference about nothrow and @safe ness of the converted expression?

As I understand it, with DIP 1033, a call to your example `get` function like `myNullable.get(123)` will deduce `DG` to be `int` (as it currently would), but it will instantiate successfully instead of failing, because `int` will now have an implicit conversion to `inout(int) delegate`. At runtime, the argument will be evaluated eagerly unless you explicitly wrap it in a delegate (e.g., with `() =>`).