July 29, 2020
On Wednesday, 29 July 2020 at 10:21:11 UTC, aberba wrote:

> I think its a good proposal.

FYI, this is an old review thread. We're preparing to launch the Final Review of this DIP and Walter was simply addressing a prior concern.

Please save new feedback for the new review!
July 29, 2020
On Tuesday, 28 July 2020 at 22:30:22 UTC, Walter Bright wrote:
>> What if I call toString with a lambda that is pure, will it force toString to be pure?
>
> No, not under this DIP.

It was not a question directed at you, but more like a rhetorical question about the alternative proposal that I answered the next sentence. This figure of speech translates poorly on text evidently, I'll try to avoid it next time.

> I suspect you're misuderstanding what the DIP proposes? The proposal does not remove any attributes from toString. It only adds the attributes from toString to the lambda.

I think I understand exactly what the DIP proposes and why, and I am in favor of making attributes in function/delegate parameters more ergonomic and going towards deprecating the `lazy` keyword.

However, there's another problem related to delegate parameters that we are trying to solve: the lack of "inout attributes". Maybe you don't relate to this problem, or you think this is material for another DIP. I think this is important though, so I'll elaborate a bit more.

Let's take the example of using opApply on a binary tree from the Dlang tour:
https://tour.dlang.org/tour/en/gems/opdispatch-opapply

Imagine someone writes a library with that tree data structure:
```
module binarytree;
class Tree {
    Tree lhs;
    Tree rhs;
    int opApply(int delegate(Tree) dg) {
        if (lhs && lhs.opApply(dg)) return 1;
        if (dg(this)) return 1;
        if (rhs && rhs.opApply(dg)) return 1;
        return 0;
    }
}
```

Great! But now one the library users opens an issue. That user wrote this code:
```
import binarytree;
int nodeCount(Tree tree) @safe @nogc pure nothrow {
    int result = 0;
    foreach(node; tree)
        result++;
    return result;
}
```

The user says "I get all these errors that I cannot call binarytree.Tree.opApply because it's impure, @system, non-@nogc, and it may throw"

The library author says "I got you. I will mark the opApply function @safe @nogc pure nothrow". Now that user is happy, but a different user opens an issue. The second user wrote this code:
```
import binarytree, std.stdio;
void main() {
    Tree tree = new Tree();
    foreach(node; tree)
        writeln(node);
}
```

The second user says "I get this weird error that I cannot pass argument __foreachbody1 of type int delegate(Tree node) @system to parameter int delegate(Tree) pure nothrow @nogc @safe dg" The library author replies "well, I could remove those attributes from opApply, but that breaks the code of the first user again".

How would you solve this if you were the library author?
To be clear: this IS a question directed at you, Walter ;)

However, the alternative proposal to DIP 1032 would solve this, making both users and the library author happy. On top of that, it would solve the issues described in DIP 1032, killing two birds with one stone. The current proposal in DIP 1032 is only killing one bird with one stone.
July 29, 2020
On Wednesday, 29 July 2020 at 08:20:14 UTC, Walter Bright wrote:
> On 7/28/2020 7:03 PM, Avrina wrote:
> "Sort of pure", "mostly pure", "pure except for the impure stuff" all mean "not pure" and is not useful.
>
> Writing pure code in D is hard because D's purity checks have teeth in them. But the teeth make it worthwhile and useful. Otherwise it would just be an empty suit.

I totally agree. If you demonstrate how the alternative proposal lets code writers cheat the `pure` attribute without the compiler catching it, I would bin the proposal immediately. Maybe you can give a code snippet of what you think would go wrong with the alternative proposal?

July 29, 2020
On Wednesday, 29 July 2020 at 08:20:14 UTC, Walter Bright wrote:
> On 7/28/2020 7:03 PM, Avrina wrote:
>> *It is* pure. The only thing that wouldn't be pure would be the delegate call.
>
> "Sort of pure", "mostly pure", "pure except for the impure stuff" all mean "not pure" and is not useful.

I've provided an example that is useful. It wouldn't be limited to pure. Having the function attributes adapt to the delegate is useful.

All this DIP does is reduce attribute, SLIGHTLY, with an odd rule at that. I'd rather the attribute be explicit on the delegate of a function. Rather than having some hacky workaround with an alias in another scope.

If the problem you are trying to solve is to reduce attribute bloat, there are better ways for this to be achieved. Of which wouldn't cause a breaking change.

>> It'd be no different than what you can already do today with lazy.
>
> Lazy is headed for deprecation because it has such unprincipled behavior.
>
> Writing pure code in D is hard because D's purity checks have teeth in them. But the teeth make it worthwhile and useful. Otherwise it would just be an empty suit.

It's not limited to pure, it would work for nothrow, @nogc, @safe, etc...

You can make the same argument for the new proposed feature, but in this case you'd still get the compiler's help in identifying when it isn't actually pure. You are literally dissecting your own argument you made against this proposal. And IIRC you viewed "lazy" as a failed experiment yet you are still defending its' flaws.

July 29, 2020
On Wed, Jul 29, 2020 at 01:11:43AM -0700, Walter Bright via Digitalmars-d wrote:
> On 7/28/2020 9:45 PM, H. S. Teoh wrote:
[...]
> > It's basically the same idea as inout: as far as the function body is concerned, it's pure (resp. const); but to the caller, it could be pure or impure (resp. immutable/mutable) depending on what was passed in. I argue that this would be much more useful than what this DIP proposes.
> 
> The only time you'd need fewer attributes on delegate is if the function never actually calls it. I submit that this is a relatively rare case, and can be handled other ways (like making the function a template and letting it infer its attributes).

It's not a rare case at all in generic code.  For instance, you're implementing iteration over some container with a callback for user-supplied operations to be run on each element.  You'd like the code to be maximally reusable, regardless of what attributes the user-supplied callback may have.  The function body itself is pure / @nogc / whatever, besides that single call to the user-supplied callback.  You'd like for the compiler to enforce purity / @nogc / etc. in the function body as a safeguard in case you screw up.

But currently, the only way to achieve this is to template the code, which means 2^N copies of *exactly the same generated code* where N is the number of attributes you'd like to support. It's just like inout: you *could* in theory do without it, just templatize the function and let it create a new instance per combination of attributes, even though the generated code is instruction-by-instruction identical. But having inout allows you to reduce this template bloat.

Furthermore, currently you cannot actually write *any* attributes on your function at all, because as soon as you write, say, 'pure' on your function, it's no longer usable from impure code (the user can no longer pass in an impure delegate, even though, as far as your function's body is concerned, the purity of the delegate is completely irrelevant). The only way to get the compiler to enforce purity checks is to use this workaround:

	auto myFunc(D)(D callback)
		if (is(D == delegate))
		{ ...  }

	pure unittest {	// N.B. unittest is marked pure
		auto dg = ... // some pure callback

		// force compiler error if myFunc is impure in spite of
		// dg being pure
		auto result = myFunc(dg);
	}

This hack is widely used in Phobos code precisely because there is currently no way to express that "this function's purity depends on the purity of the callback argument".

And it's not even a foolproof workaround, because if you don't compile with -unittest, the check is skipped.


T

-- 
To err is human; to forgive is not our policy. -- Samuel Adler
July 29, 2020
On 7/29/2020 9:30 AM, H. S. Teoh wrote:
> the only way to achieve this is to template the code,
> which means 2^N copies of*exactly the same generated code*  where N is
> the number of attributes you'd like to support.

The right way to solve that problem is for the compiler to merge functions that are semantically identical, because there are a lot more instances of that than in this example.

Having a messy compiler feature for it is not the best.

P.S. gdc/ldc may already do this as C++ has the same issue

> This hack is widely used in Phobos code precisely because there is
currently no way to express that "this function's purity depends on the
purity of the callback argument".

Whenever one needs to test attribute inference, you'll need such tests. There's also passing a function as a template alias parameter.
July 29, 2020
On 7/29/2020 4:26 AM, Dennis wrote:
> How would you solve this if you were the library author?

The usual way is with templates.
July 30, 2020
On Thursday, 30 July 2020 at 03:16:02 UTC, Walter Bright wrote:
> On 7/29/2020 4:26 AM, Dennis wrote:
>> How would you solve this if you were the library author?
>
> The usual way is with templates.

It doesn't work for `Object.toString` which is the textbook use case for this.
July 30, 2020
On Thursday, 30 July 2020 at 02:03:56 UTC, Walter Bright wrote:
>
> The right way to solve that problem is for the compiler to merge functions that are semantically identical, because there are a lot more instances of that than in this example.
>
> Having a messy compiler feature for it is not the best.
>
> P.S. gdc/ldc may already do this as C++ has the same issue

The frontend will reject the code before it makes it to the IR/backend, on the ground of mismatched attributes.
July 29, 2020
On 7/29/2020 8:23 PM, Mathias LANG wrote:
> On Thursday, 30 July 2020 at 03:16:02 UTC, Walter Bright wrote:
>> On 7/29/2020 4:26 AM, Dennis wrote:
>>> How would you solve this if you were the library author?
>>
>> The usual way is with templates.
> 
> It doesn't work for `Object.toString` which is the textbook use case for this.

That's really a problem with Object.toString.