April 07, 2020
On 4/6/20 5:11 PM, Walter Bright wrote:
> On 4/6/2020 5:46 AM, Steven Schveighoffer wrote:
>> i.e., if both are pure, the function is pure. If either one is not pure, the function is not pure.
>>
>> if both are @nogc, the function is considered @nogc. If either is not @nogc, the function is not considered @nogc.
> 
> That's more or less what the DIP proposes. The delegate parameter defaults to being at least as restrictive as the function it is declared in. The delegate parameter can be made more restrictive by adding attributes to it directly.

No, the function can't be called from such a caller that creates a delegate.

For example, if you have:

void foo(void delegate() dg) { dg(); }

Let's say I'm in a @nogc function, and I want foo to call my local lambda @nogc delegate. Sure, I can pass in a delegate that is @nogc to this function, because of the implicit cast allowed. but I can't actually call the function from that context! It becomes useless. Then I need 2^n copies of foo, one for each possible set of attributes.

This is why we use templates for such things, but really, it would make a whole lot more sense to avoid generating identical code for 2^n calls (which is what a template of that kind would do), and you would get for free the fact that the delegate's attributes match the function call's attributes.

> To make the delegate parameter less restrictive, it would need to be declared using an alias for the type, as shown in the DIP.
> 
> The point of this is so that the function can call the delegate, which is far and away the usual use case. The unusual use case is storing the delegate somewhere else for someone else to call.

First, I disagree that it's unusual to store delegates. Far and away D code that calls a function you pass in uses aliases, which require templates, which infer all attributes anyway. But you have to use a delegate when you need to store it somewhere for later.

Second, I understand that there are ways to get this behavior, but it seems like if you are going to solve the "delegates that are called presently" problem (i.e. the lazy issue), you would be better to solve it in a way that doesn't require dozens of boilerplate repetition.

> The DIP does not propose that the delegate parameter infer its attributes based on the function's body. To implement that would require an iterative approach, and that is not worth the complexity.

That's not what's being discussed. There is no requirement to infer anything from the function body. All that is required is to examine the set of attributes defined on both the function and the delegate, and logic-or them (or logic-and them, I don't really know).

-Steve
April 10, 2020
On 4/7/2020 2:36 PM, Steven Schveighoffer wrote:
> For example, if you have:
> 
> void foo(void delegate() dg) { dg(); }
> 
> Let's say I'm in a @nogc function, and I want foo to call my local lambda @nogc delegate. Sure, I can pass in a delegate that is @nogc to this function, because of the implicit cast allowed. but I can't actually call the function from that context! It becomes useless.

This is still asking for (in effect) making foo() pure if the delegate is pure. This is just not going to work if foo() does much more than just call the delegate. Not many non-trivial functions can be pure, but delegates often are pure, because delegates are often trivial.

In order for your proposal to work, foo()'s implementation has to always be the most restrictive, i.e. it has to be `@safe nothrow pure @nogc`.
April 10, 2020
On Friday, 10 April 2020 at 09:40:12 UTC, Walter Bright wrote:
> This is still asking for (in effect) making foo() pure if the delegate is pure.

The proposal is that the delegate can REMOVE attributes from foo() but never ADD them.

Imagine this code:
```
class MyClass
{
    @nogc @safe pure nothrow
    void toString(void delegate(const(char)[]) sink)
    {
        sink("MyClass"); // today: error! sink is not @nogc @safe pure nothrow
    }
}
```

This does not compile today because `sink` does not have the right attributes.

- your proposal says: *add* the attributes of `toString` to `sink`. The problem is that you now have a very restrictive sink function. To support impure / @gc / @system / throw sink functions you need an exponential amount of overloads to support every subset. You can't use a template either since it is a virtual function.

- the alternative proposal says: let `sink` *remove* attributes from toString if necessary. If I call toString with a sink that does printf, toString will lose the pure and @safe for that specific call. If I call toString with a sink that appends to an array, it will lose the @nogc for that call.

Now let's see what happens in your scenario:
```
class MyClass
{
    @safe
    void toString(void delegate(const(char)[]) sink)
    {
        writeln("bye bye pure, @nogc, nothrow");
        sink("MyClass");
    }
}
```
What if I call toString with a lambda that is pure, will it force toString to be pure?
No, because the sink can only temporarily *remove* attributes from toString, not add them.
Since toString was not pure to begin with, it does not matter whether sink is pure either.

I hope this makes it clearer.
April 10, 2020
On 4/10/20 5:40 AM, Walter Bright wrote:
> On 4/7/2020 2:36 PM, Steven Schveighoffer wrote:
>> For example, if you have:
>>
>> void foo(void delegate() dg) { dg(); }
>>
>> Let's say I'm in a @nogc function, and I want foo to call my local lambda @nogc delegate. Sure, I can pass in a delegate that is @nogc to this function, because of the implicit cast allowed. but I can't actually call the function from that context! It becomes useless.
> 
> This is still asking for (in effect) making foo() pure if the delegate is pure. This is just not going to work if foo() does much more than just call the delegate. Not many non-trivial functions can be pure, but delegates often are pure, because delegates are often trivial.

No, you don't "make" foo pure, it is specifically marked pure.

In other words, you have:

void foo(void delegate() @called) pure @nogc @safe { dg(); }

So the @called attribute says "foo calls dg", which means that for a specific call of foo, you can strip off attributes that it doesn't have.

So if you pass in a @nogc pure delegate, that call of foo is considered @nogc pure. If you pass in a @safe pure delegate, that call of foo becomes @safe pure. If you pass in a nothrow delegate, it does NOT become nothrow, because the function did not declare that everything other than the delegate call is nothrow.

> 
> In order for your proposal to work, foo()'s implementation has to always be the most restrictive, i.e. it has to be `@safe nothrow pure @nogc`.

It has to be as restrictive as declared, just like usual. It can never gain attributes, but can only remove them.

I still think that either the DIP as written or this alternative proposal would be better with an opt-in attribute such as @called.

-Steve
April 10, 2020
On 4/10/2020 5:22 AM, Steven Schveighoffer wrote:
> No, you don't "make" foo pure, it is specifically marked pure.
> 
> In other words, you have:
> 
> void foo(void delegate() @called) pure @nogc @safe { dg(); }
> 
> So the @called attribute says "foo calls dg", which means that for a specific call of foo, you can strip off attributes that it doesn't have.

Sorry, that's still another way of saying the same thing - foo()'s implementation has to be pure-compatible. This is impractical for reasons mentioned.
April 10, 2020
On Fri, Apr 10, 2020 at 05:05:45PM -0700, Walter Bright via Digitalmars-d wrote:
> On 4/10/2020 5:22 AM, Steven Schveighoffer wrote:
> > No, you don't "make" foo pure, it is specifically marked pure.
> > 
> > In other words, you have:
> > 
> > void foo(void delegate() @called) pure @nogc @safe { dg(); }
> > 
> > So the @called attribute says "foo calls dg", which means that for a specific call of foo, you can strip off attributes that it doesn't have.
> 
> Sorry, that's still another way of saying the same thing - foo()'s implementation has to be pure-compatible.
[...]

It doesn't "have to be". Nobody says foo *must* be pure.

What we're saying is that foo *can* be pure if its implementation is pure, in which case if the delegate is pure, then calling foo with a pure delegate will be treated as pure.  If foo is not pure to begin with, then it will remain impure, regardless of what the delegate is.


T

-- 
Sometimes the best solution to morale problems is just to fire all of the unhappy people. -- despair.com
April 10, 2020
On Fri, Apr 10, 2020 at 05:05:45PM -0700, Walter Bright via Digitalmars-d wrote:
> On 4/10/2020 5:22 AM, Steven Schveighoffer wrote:
> > No, you don't "make" foo pure, it is specifically marked pure.
> > 
> > In other words, you have:
> > 
> > void foo(void delegate() @called) pure @nogc @safe { dg(); }
> > 
> > So the @called attribute says "foo calls dg", which means that for a specific call of foo, you can strip off attributes that it doesn't have.
> 
> Sorry, that's still another way of saying the same thing - foo()'s implementation has to be pure-compatible. This is impractical for reasons mentioned.

And since we appear to be talking past each other, let me enumerate exactly what we mean:

Case 1:	body of foo is pure, dg is impure.  Result: foo(dg) is impure.

Case 2: body of foo is pure, dg is pure. Result: foo(dg) is pure.

Case 3: body of foo is impure, dg is impure.  Result: foo(dg) is impure.

Case 4: body of foo is impure, dg is pure. Result: foo(dg) is impure.

There is no requirement placed upon foo besides what its author has already imposed upon it.


T

-- 
Be in denial for long enough, and one day you'll deny yourself of things you wish you hadn't.
April 11, 2020
On 4/10/20 8:05 PM, Walter Bright wrote:
> On 4/10/2020 5:22 AM, Steven Schveighoffer wrote:
>> No, you don't "make" foo pure, it is specifically marked pure.
>>
>> In other words, you have:
>>
>> void foo(void delegate() @called) pure @nogc @safe { dg(); }
>>
>> So the @called attribute says "foo calls dg", which means that for a specific call of foo, you can strip off attributes that it doesn't have.
> 
> Sorry, that's still another way of saying the same thing

I meant that the compiler doesn't check for purity in the surrounding code *based on the call*, it's already checked *when it is compiled*:

To drive the point home:

--- a.di
module a;
void foo(void function() @called fn) pure;
---
module b;
import a;

void bar1() pure {}
void bar2() {} // not pure

void fun1() pure
{
   foo(&bar1); // OK
   // foo(&bar2); // Error, cannot call impure foo
}

void fun2()
{
   foo(&bar1); // OK, can call pure from impure function
   foo(&bar2); // also OK, foo now becomes impure.
}
---

Note that the implementation of foo isn't relevant. Everything OTHER THAN the function pointer call is checked for purity, because it's marked pure. If the non-function pointer call code is not pure, it won't compile. No new situations can happen because it's already done compiling.

> foo()'s implementation has to be pure-compatible. This is impractical for reasons mentioned.

foo()'s implementation is pure compatible, because the compiler checked it. What is difficult or impractical about this? We already do this today -- you mark a function pure, the compiler verifies that it is.

I don't know what you mean by "impractical" or "reasons mentioned". Nothing in our previous dialog points to you understanding this proposal. In particular this comment:

> This is just not going to work if foo() does much more than just call the delegate.

reads like you think the compiler is incapable of checking for purity, which I know you don't think that.

If you want to present an argument against this, one such "because I don't like it" is perfectly fine. After all, you are the one deciding the result. If you want to show objectively that it's not workable or inferior, more words than "reasons mentioned" are needed. What reasons? Where are they mentioned?

-Steve
May 20, 2020
Answering the DIP authors message at the feedback theard

On Wednesday, 20 May 2020 at 05:58:23 UTC, Walter Bright wrote:
> On 4/3/2020 3:23 PM, Dukc wrote:
>> I completely disagree with the notion that delegates with conventional syntax should inherit the attributes of the function. First, the code breakage is going to be high relative to the benefit.
>
> That would only be true if the function never calls the delegate (i.e. it only stores the delegate elsewhere), which does happen but is not the usual use.

True, but considering for long the feature has been around, I can't see this reducing the damage enough. If we were in alpha state doing D3, I wouldn't be worried. But for a stable language it's a desperate move to rely on assumptions like that. The additional attribute soup that would result from Jonathan Marlers proposal at the feedback theard may be bad, but less so than the breakage you're proposing.

And while I am at it, remember that the Marlers proposal will still in a way reduce attribute soup, because

```
@safe pure nothrow fun(@called void delegate(int) a)
```

is better than


```
@safe pure nothrow fun(@safe pure nothrow void delegate(int) a)
```
(and does not require `a` to always be `@safe pure nothrow`. See later why.)

>
>
>> Second, we are talking about adding a special case to the language semantics, that is likely going to be hard to understand and thus, to learn and remember.
>
> On the contrary, it will likely not even be noticed. For example, it only makes sense that a pure function would need its delegate parameters to also be pure so it can call them. It's annoying to have to specify `pure` twice.
>

I was talking about the reverse case. Someone wants to just store the delegate somewhere. We would have to explain why he/she needs to alias the function pointer or delegate separately. After all, normally

```
alias Y = X;
void fun(Y a);
```

behaves the same as

```
void fun(X a);
```

. Your proposal would add a special case to that rule.

>
>> If this proposal is changed to only propose this change to `lazy` parameters, it might just be worth considering. `lazy` is already kind of "special" in it's behaviour so I could see the special casing pshycologically easier to accept there. But even there I'm sceptical.
>
> The idea is to get rid of the special cases of lazy.
>

I quess that moves to the territory of your other dip, that wants to make every possible parameter `lazy`. No need to discuss that here.

>
>> What I'm saying next will be off the scope of the DIP, but I say it because of the possibility that the DIP is unintentionally trying to solve the wrong problem. The biggest problem with delegates in attributes is not that they don't infer the attributes from the called function -vice versa! In the ideal world, the called function would infer it's attributes from the delegate, not unlike how `inout` function infers it's return value constness from constness of the `inout` parameter.
>
> Inferring function attributes from the delegate argument is impractical. For example, many delegates are trivial lambda functions, often inferred as pure. But the functions that call those delegates can rarely be pure. In effect, the function would have to be compilable with the *tightest* combination of attributes every time.

No. The idea is not to infer new attributes from the delegate parameter. The idea is to infer which attributes the function CAN RETAIN when it calls the delegate. For example:

```
@safe pure nothrow fun(@called void delegate(int) a)
```

If this was called with a being `@safe pure nothrow @nogc` delegate, the function call would be `@safe pure nothrow`, but not `@nogc`. On the other hand, if `a` is `@system nothrow`, the function call will be inferred as `@system nothrow`.

So only those attributes the function can comply with are inferred, not others. The above example function could still use the garbage collector.
May 20, 2020
On Wednesday, 20 May 2020 at 10:15:41 UTC, Dukc wrote:
> And while I am at it, remember that the Marlers proposal will still in a way reduce attribute soup, because
>
> ```
> @safe pure nothrow fun(@called void delegate(int) a)
> ```

Remembered wrong. Marler proposed `@inherit`, not `@called`.