April 04, 2020
On 4/3/2020 4:05 PM, Jonathan Marler wrote:
> If this were implemented, I'd prefer for it to be "opt-in" with an attribute such as @inherit (https://forum.dlang.org/post/plvqxehkjzxkvhsacjwp@forum.dlang.org), however, even if it was opt-in, I'm still not a huge fan of this feature.  Reason being that I favor making code "easier to read" rather than "easier to write".  Adding a new "attribute inheritance" semantic, whether implicitly or explicitly adds cognitive burden to the reader.  You now have to have extra knowledge about when attributes are inherited, whereas with the status quo, you don't have to think about it because all attributes are explicit.  And even if we make it opt in, now you have to learn a new syntax and/or attribute and how it works.  For me the benefit doesn't really justify the added complexity.

We can't solve every problem with adding ever more attributes. I kinda regard every attribute we wind up adding as an admission of defeat.

The idea is the user won't have to think about the delegate attributes in the most common cases, it will "just work".

> P.S. What about delegate types defined inside functions? Would they also inherit the functions attributes? @safe pure nothrow void foo() { alias D = void delegate(); D d; }

I should think so, otherwise how could the function call it?

April 04, 2020
On 4/3/2020 5:09 PM, H. S. Teoh wrote:
> IOW, it should be the *function* that inherits attributes from the
> passed-in delegate, not the other way round as proposed by this DIP.

Then you've got the problem of two delegate parameters with contradictory attributes.

But worse, lambdas get their attributes inferred. Pass a simple lambda, and suddenly the function it gets passed to must be pure? This isn't going to work.

April 04, 2020
On Friday, 3 April 2020 at 23:05:52 UTC, Jonathan Marler wrote:

> (https://forum.dlang.org/post/plvqxehkjzxkvhsacjwp@forum.dlang.org), however, even if it was opt-in, I'm still not a huge fan of this feature.  Reason being that I favor making code "easier to read" rather than "easier to write".  Adding a new "attribute inheritance" semantic, whether implicitly or explicitly adds cognitive burden to the reader.  You now have to have extra knowledge about when attributes are inherited, whereas with the status quo, you don't have to think about it because all attributes are explicit.  And even if we make it opt in, now you have to learn a new syntax and/or attribute and how it works.  For me the benefit doesn't really justify the added complexity.

I totally agree with Jonathan: it's adding more complexity to the language without any specific reason, and D is complex enough IMHO.

Python has learned that long time ago: "Explicit is better than implicit".

I also don't agree with the rationale:

"is both surprising and burdensome" => opinabile, not for me for example, I don't find it surprising at all.
"will be welcome" => idem




April 04, 2020
On Sat, Apr 04, 2020 at 12:18:54AM -0700, Walter Bright via Digitalmars-d wrote:
> On 4/3/2020 5:09 PM, H. S. Teoh wrote:
> > IOW, it should be the *function* that inherits attributes from the passed-in delegate, not the other way round as proposed by this DIP.
> 
> Then you've got the problem of two delegate parameters with contradictory attributes.

That's not a problem, the function inherits the most permissive of the two.


> But worse, lambdas get their attributes inferred. Pass a simple lambda, and suddenly the function it gets passed to must be pure? This isn't going to work.

I think you misunderstood.  Or I explained poorly.  What I have in mind is the following.

To make it absolutely clear and unambiguous, I will assign a numerical value to each attribute (this is just for explanatory purposes, it does not imply actual implementation):

	impure = 0, pure = 1
	throws = 0, nothrow = 1
	@system = 0, @safe/@trusted = 1
	allocating = 0, @nogc = 1

Note that 0 is assigned to the most permissive value, and 1 is assigned to the most restrictive.

For argument's sake, let's say a function's attributes is a bitmask consisting of the above values, in the above order.  So a function marked pure nothrow @safe would correspond with the bitmask 0b1110.

If the function has one or more delegate parameters, then the *effective* attribute set of a particular call to that function is the bitwise AND of its own bitmask and the bitmask(s) of its delegate argument(s).

For example, if the function is pure nothrow @safe, which corresponds to the bitmask 0b1110, and it's called with an impure, but otherwise nothrow, @safe, @nogc delegate, corresponding with the bitmask 0b0111, then its effective attribute set would be 0b1110 & 0b0111 == 0x0110, i.e., nothrow @safe.

If the same function called with two delegates, with the first one pure @safe but throws and allocates (i.e., 0b1010) and the second impure and throwing but @safe @nogc (i.e., 0b0011), then that function call behaves as if the function's attribute bitmask is 0b1110 & 0b1010 & 0b0011 == 0b0010, that is, @safe (but impure, throwing, and allocating).


IOW, the effective attributes of a function call is the least permissive among the function itself and its delegate arguments.


T

-- 
Elegant or ugly code as well as fine or rude sentences have something in common: they don't depend on the language. -- Luca De Vitis
April 04, 2020
On Saturday, 4 April 2020 at 11:40:26 UTC, H. S. Teoh wrote:
> ...
> IOW, the effective attributes of a function call is the least permissive among the function itself and its delegate arguments.

This is exactly the behaviour that I want.

In general, every function should be annotated by default with the the strongest gaurantees which the compiler can verify that it actually fulfills. There are only two exceptions to this rule, I think:

A) When insufficient information is available to the compiler to determine if a restrictive (strong gaurantee) attribute applies, the programmer may need to supply the right answer explicitly, as in the case of @trusted, or of abstract and extern functions whose body is not available for analysis.

B) When the author of a public API chooses to explicitly relax these gaurantees in order to avoid exposing private implementation details and accidentally turning changes to otherwise encapsulated details into breaking API changes.

Currently, I find D's rules for attribute defaults and scoping so far out of sync with the logical system above that I've mostly given up on it and just explicitly annotate EVERYTHING I can. This is fairly painful due to the visual noise and the lack of negative attributes like "impure".

The semantics of D's attribute system are great - a major selling point of the language, to me - but the syntax is terribly awkward, verbose, and inconsistent.
April 06, 2020
On 04.04.20 09:15, Walter Bright wrote:
>> P.S. What about delegate types defined inside functions? Would they also inherit the functions attributes? @safe pure nothrow void foo() { alias D = void delegate(); D d; }
> 
> I should think so,

No, please.

> otherwise how could the function call it?

Easy:

void foo()pure{
    alias D = int delegate();
    enum D d = ()=>2;
    enum x = d();
}

Also, there is no need to call it:

auto foo()pure{
    alias D = int delegate();
    D d = ()=>2;
    return d;
}
April 06, 2020
On 4/4/2020 4:40 AM, H. S. Teoh wrote:
> IOW, the effective attributes of a function call is the least permissive
> among the function itself and its delegate arguments.

I.e. pass a pure lambda, and the function must be pure. Like I said, that is not going to work.
April 06, 2020
On 06.04.20 10:34, Walter Bright wrote:
> On 4/4/2020 4:40 AM, H. S. Teoh wrote:
>> IOW, the effective attributes of a function call is the least permissive
>> among the function itself and its delegate arguments.
> 
> I.e. pass a pure lambda, and the function must be pure. Like I said, that is not going to work.

The IOW is an inaccurate summary of the post, probably a typo. The actual suggestion is to give it the _most_ permissive attributes.
April 06, 2020
On 4/6/20 5:58 AM, Timon Gehr wrote:
> On 06.04.20 10:34, Walter Bright wrote:
>> On 4/4/2020 4:40 AM, H. S. Teoh wrote:
>>> IOW, the effective attributes of a function call is the least permissive
>>> among the function itself and its delegate arguments.
>>
>> I.e. pass a pure lambda, and the function must be pure. Like I said, that is not going to work.
> 
> The IOW is an inaccurate summary of the post, probably a typo. The actual suggestion is to give it the _most_ permissive attributes.

The point is you have two sets of attributes, the declared attributes, and the lambda attributes. The resulting attributes for the function are going to be the most restrictive attributes that can call both.

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.

etc.

You need a specialized tag on the lambda/function for this to work.

This is essentially what I was proposing here: https://forum.dlang.org/post/r67lq7$th1$1@digitalmars.com

-Steve
April 06, 2020
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.

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.

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.