www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Discussion Thread: DIP 1032--Function pointers and Delegate

reply Mike Parker <aldacron gmail.com> writes:
This is the discussion thread for the first round of Community 
Review of DIP 1032, "Function pointers and Delegate Parameters 
Inherit Attributes from Function":

https://github.com/dlang/DIPs/blob/0c99bd854302ade3e6833080410e9050fddec346/DIPs/DIP1032.md

The review period will end at 11:59 PM ET on April 17, or when I 
make a post declaring it complete. Discussion in this thread may 
continue beyond that point.

Here in the discussion thread, you are free to discuss anything 
and everything related to the DIP. Express your support or 
opposition, debate alternatives, argue the merits, etc.

However, if you have any specific feedback on how to improve the 
proposal itself, then please post it in the feedback thread. The 
feedback thread will be the source for the review summary I write 
at the end of this review round. I will post a link to that 
thread immediately following this post. Just be sure to read and 
understand the Reviewer Guidelines before posting there:

https://github.com/dlang/DIPs/blob/master/docs/guidelines-reviewers.md

And my blog post on the difference between the Discussion and 
Feedback threads:

https://dlang.org/blog/2020/01/26/dip-reviews-discussion-vs-feedback/

Please stay on topic here. I will delete posts that are 
completely off-topic.
Apr 03
next sibling parent Mike Parker <aldacron gmail.com> writes:
On Friday, 3 April 2020 at 10:30:33 UTC, Mike Parker wrote:

 However, if you have any specific feedback on how to improve 
 the proposal itself, then please post it in the feedback 
 thread. The feedback thread will be the source for the review 
 summary I write at the end of this review round. I will post a 
 link to that thread immediately following this post.
The Feedback thread is here: https://forum.dlang.org/post/tkosvxedhztfjxsxtkdm forum.dlang.org
Apr 03
prev sibling next sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
The inheritance rationale also applies to aggregates btw, which 
if done would obviate the "by default" changes btw.

Once this change is made, the aggregate status quo will stand out 
even more as a weird inconsistency.

Moreover, what if you need to invert the attributes? Yes, the 
same thing comes up again.

nothrow
int makeNothrow(void delegate() dg) {
     try {
       dg();
       return 0;
     } catch(Exception e) {
       return e.toErrorCode();
     }
}


Obviously, a nothrow dg there defeats the purpose of that 
function... so how do you tell it you want a throws dg?

There's an easy fix here, something we've wanted for ages, but 
this change makes it all the more necessary.
Apr 03
next sibling parent Dennis <dkorpel gmail.com> writes:
On Friday, 3 April 2020 at 12:58:24 UTC, Adam D. Ruppe wrote:
 Obviously, a nothrow dg there defeats the purpose of that 
 function... so how do you tell it you want a throws dg?
That can be accomplished with the alias trick described in "Breaking Changes and Deprecations".
Apr 03
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 4/3/2020 5:58 AM, Adam D. Ruppe wrote:
 so how do you tell it you want a throws dg?
This is covered in the DIP.
Apr 03
parent reply Dukc <ajieskola gmail.com> writes:
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
parent Dukc <ajieskola gmail.com> writes:
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`.
May 20
prev sibling next sibling parent Mathias LANG <geod24 gmail.com> writes:
On Friday, 3 April 2020 at 10:30:33 UTC, Mike Parker wrote:
 This is the discussion thread for the first round of Community 
 Review of DIP 1032, "Function pointers and Delegate Parameters 
 Inherit Attributes from Function":

 https://github.com/dlang/DIPs/blob/0c99bd854302ade3e6833080410e9050fddec346/DIPs/DIP1032.md
Already gave my review on it: https://github.com/dlang/DIPs/pull/170#pullrequestreview-294073723 I would very much welcome a way to express a relationship between the delegate's attributes and the function's attributes. But this piece of magic ? No thanks. The change itself is bad on its own, and only marginally useful if the `lazy` case is the only one considered.
Apr 03
prev sibling next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
Continuing the discussion from 
https://forum.dlang.org/post/r688sq$2ism$1 digitalmars.com

 Delegate/functionpointer parameters do not infer safe/nogc/pure/nothrow from
their use inside the function.
No of course they do not. But S.foo infers it's attributes from the function body. S.foo is not a template function, so I would assume this passes to the delegate. Are you suggesting that: struct S(T) { T delegate() _dg; void foo(T delegate() dg) { _dg = dg; } } behaves differently than: struct S { int delegate() _dg; void foo(int delegate() dg) safe pure nogc nothrow {_dg = dg;} } ? in other words, in both cases, you can call from safe pure nogc nothrow context, but the parameter in the first case is T delegate() (with no attributes), but the second, the parameter becomes int delegate() safe pure nothrow nogc? There needs to be a discussion of templates in the DIP, especially when template attribute inference can and can not affect the delegate attribute requirements. -Steve
Apr 03
parent Walter Bright <newshound2 digitalmars.com> writes:
On 4/3/2020 3:07 PM, Steven Schveighoffer wrote:
 Continuing the discussion from 
 https://forum.dlang.org/post/r688sq$2ism$1 digitalmars.com
 
 Delegate/functionpointer parameters do not infer safe/nogc/pure/nothrow from 
 their use inside the function.
No of course they do not. But S.foo infers it's attributes from the function body. S.foo is not a template function, so I would assume this passes to the delegate. Are you suggesting that: struct S(T) {    T delegate() _dg;    void foo(T delegate() dg) { _dg = dg; } } behaves differently than: struct S {    int delegate() _dg;    void foo(int delegate() dg) safe pure nogc nothrow {_dg = dg;} } ?
Yes. (Otherwise the inference would have to be iterative, which I'd really like to avoid.)
 
 in other words, in both cases, you can call from  safe pure  nogc nothrow 
 context, but the parameter in the first case is T delegate() (with no 
 attributes), but the second, the parameter becomes int delegate()  safe pure 
 nothrow  nogc?
 
 There needs to be a discussion of templates in the DIP, especially when
template 
 attribute inference can and can not affect the delegate attribute requirements.
Your example is a good one, and I'll include it.
Apr 04
prev sibling parent reply Jonathan Marler <johnnymarler gmail.com> writes:
On Friday, 3 April 2020 at 10:30:33 UTC, Mike Parker wrote:
 This is the discussion thread for the first round of Community 
 Review of DIP 1032, "Function pointers and Delegate Parameters 
 Inherit Attributes from Function":

 https://github.com/dlang/DIPs/blob/0c99bd854302ade3e6833080410e9050fddec346/DIPs/DIP1032.md

 The review period will end at 11:59 PM ET on April 17, or when 
 I make a post declaring it complete. Discussion in this thread 
 may continue beyond that point.

 Here in the discussion thread, you are free to discuss anything 
 and everything related to the DIP. Express your support or 
 opposition, debate alternatives, argue the merits, etc.

 However, if you have any specific feedback on how to improve 
 the proposal itself, then please post it in the feedback 
 thread. The feedback thread will be the source for the review 
 summary I write at the end of this review round. I will post a 
 link to that thread immediately following this post. Just be 
 sure to read and understand the Reviewer Guidelines before 
 posting there:

 https://github.com/dlang/DIPs/blob/master/docs/guidelines-reviewers.md

 And my blog post on the difference between the Discussion and 
 Feedback threads:

 https://dlang.org/blog/2020/01/26/dip-reviews-discussion-vs-feedback/

 Please stay on topic here. I will delete posts that are 
 completely off-topic.
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. 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; }
Apr 03
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
 On Friday, 3 April 2020 at 10:30:33 UTC, Mike Parker wrote:
 This is the discussion thread for the first round of Community
 Review of DIP 1032, "Function pointers and Delegate Parameters
 Inherit Attributes from Function":
 
 https://github.com/dlang/DIPs/blob/0c99bd854302ade3e6833080410e9050fddec346/DIPs/DIP1032.md
 
 The review period will end at 11:59 PM ET on April 17, or when I
 make a post declaring it complete. Discussion in this thread may
 continue beyond that point.
 
 Here in the discussion thread, you are free to discuss anything and
 everything related to the DIP. Express your support or opposition,
 debate alternatives, argue the merits, etc.
[...] I echo Dukc's concerns from the other thread, that this DIP seems to be approaching the problem in the wrong way. What I often find myself needing is not that delegates inherit attributes from the function it's passed to, but rather the reverse: that the *function* inherits the attributes of the passed-in delegate. The whole point of a delegate is to insert arbitrary code into a function at certain points, so usually one wants maximum freedom for the caller to pass in a delegate with any set of attributes. Why limit what the caller can do? If the caller wants to pass in a system delegate, then so be it, let him do whatever he wants. From the POV of a library author, I want my code to be maximally reusable, so as much as possible I don't want to limit what my callers can pass to me. What *I*, the function implementor, am concerned with is that *my* code, that is, the function body besides the calls to the delegate, conforms to certain attributes, such as safe. By doing so, I make it possible for safe callers to use my function -- if they pass in a safe delegate, then my function should be safe. If the delegate they pass in is system, then obviously my function should also be system. I've almost never found a need for my function to impose restrictions on a passed-in delegate -- the delegate is the caller's problem, I don't care what they do with it. I want my function to accomodate both safe and system delegates, and I want my function to be usable with both safe and system callers (the former case conditional upon the delegate being safe). Why would I want to limit what kind of delegates the caller passes to me? If the delegate does something bad, that's the caller's problem, not mine. My concern is only that my code accomodates callers with any attribute sets, whether they can actually call me with that attribute set is up to them -- by ensuring the delegate they give me conforms to said attributes. It's not my function's responsibility to enforce what the caller does with the delegate; it's the caller's responsibility to give me a delegate with the attributes that the caller wants from my function. IOW, it should be the *function* that inherits attributes from the passed-in delegate, not the other way round as proposed by this DIP. T -- When solving a problem, take care that you do not become part of the problem.
Apr 03
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
Apr 04
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
Apr 04
next sibling parent tsbockman <thomas.bockman gmail.com> writes:
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.
Apr 04
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
Apr 06
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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.
Apr 06
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
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
Apr 06
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
Apr 06
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
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
Apr 07
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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`.
Apr 10
next sibling parent reply Dennis <dkorpel gmail.com> writes:
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.
Apr 10
parent Walter Bright <newshound2 digitalmars.com> writes:
On 4/10/2020 3:27 AM, Dennis wrote:
 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.
That's right.
 - 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.
Supporting every subset is not necessary. If you don't want toString() to be restrictive, do not add attributes that make it restrictive.
 - 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.
This makes the attributes added to toString ineffectual. Worse, they would be silently removed under your proposal. The toString's caller would think they are calling a pure function, may rely on it being pure, but the pure got silently removed. This is not workable.
 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, not under this DIP.
 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.
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. Silently removing attributes from toString is not workable.
Jul 28
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
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
Apr 10
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
Apr 10
next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
Apr 10
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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.
Apr 10
prev sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
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
Apr 11
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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?
Apr 04
parent Timon Gehr <timon.gehr gmx.ch> writes:
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; }
Apr 05
prev sibling parent Paolo Invernizzi <paolo.invernizzi gmail.com> writes:
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
Apr 04