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 2020
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 2020
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 2020
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 2020
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 2020
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 2020
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 2020
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 2020
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 2020
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 2020
prev sibling next 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 2020
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 2020
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 2020
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 2020
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 2020
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 2020
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 2020
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 2020
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 2020
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 2020
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 2020
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 2020
parent reply 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 2020
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Tue, Jul 28, 2020 at 03:30:22PM -0700, Walter Bright via Digitalmars-d wrote:
 On 4/10/2020 3:27 AM, Dennis wrote:
[...]
 - 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 a misunderstanding of what was proposed. What was proposed is that the compiler will treat such a call as if it were no longer pure ( safe, nothrow, etc.). I.e., if the caller was marked pure, this would trigger a compile error that it cannot call an impure function. If the caller was impure to begin with, then it doesn't matter anyway. We are not proposing that the compiler will continue to pretend that the function is pure in spite of having secretly removed attributes from it. What we are proposing is that passing an impure delegate to the function breaks its purity, so such a call is treated as if it were impure. If the caller was expecting purity (it was marked pure), this will result in a compile error. But pure continues to apply inside the function body (except for the call to the delegate). Passing a pure delegate to the function does not break purity and therefore can be treated as a call to a pure function (i.e., pure code is allowed to call the function as long as the delegate is also pure). IOW, the callee adapts itself to the attributes of the passed delegate, rather than the other way round. The other case, which is what this DIP proposes, is of limited usefulness; all it does is to save some typing in the function signature, and not much else. If we wanted the function to be callable both from pure and impure code (with impure delegates) under this DIP, we would have to write two copies of the function that are identical except in attributes. This is redundant and not useful. What we propose -- that the function adapts itself to the attributes of the passed delegate -- makes it possible to reuse the same function for both pure and impure callers and avoid this code duplication, and at the same time without breaking any guarantees conferred by the attributes. In essence, it's the analogue of inout for pure, nothrow, safe, etc.. T -- It's amazing how careful choice of punctuation can leave you hanging:
Jul 28 2020
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 7/28/2020 4:00 PM, H. S. Teoh wrote:
 This is a misunderstanding of what was proposed.  What was proposed is
 that the compiler will treat such a call as if it were no longer pure
 ( safe, nothrow, etc.). I.e., if the caller was marked pure, this would
 trigger a compile error that it cannot call an impure function.  If the
 caller was impure to begin with, then it doesn't matter anyway.
Thanks for the clarification. But it still tricks the user into thinking the function is pure, since it says right there it is pure. Pure isn't just an attribute for the compiler, it's for the user as it offers a guarantee about what the interface to a function is. Silently removing pure can also make the user think that a function is thread-safe when it is not. Adding a feature that silently disables an explicitly placed "pure" attribute is going to become a hated misfeature. I strongly oppose it.
Jul 28 2020
next sibling parent reply Avrina <avrina12309412342 gmail.com> writes:
On Wednesday, 29 July 2020 at 01:21:44 UTC, Walter Bright wrote:
 On 7/28/2020 4:00 PM, H. S. Teoh wrote:
 This is a misunderstanding of what was proposed.  What was 
 proposed is
 that the compiler will treat such a call as if it were no 
 longer pure
 ( safe, nothrow, etc.). I.e., if the caller was marked pure, 
 this would
 trigger a compile error that it cannot call an impure 
 function.  If the
 caller was impure to begin with, then it doesn't matter anyway.
Thanks for the clarification. But it still tricks the user into thinking the function is pure, since it says right there it is pure. Pure isn't just an attribute for the compiler, it's for the user as it offers a guarantee about what the interface to a function is. Silently removing pure can also make the user think that a function is thread-safe when it is not. Adding a feature that silently disables an explicitly placed "pure" attribute is going to become a hated misfeature. I strongly oppose it.
It doesn't trick them. *It is* pure. The only thing that wouldn't be pure would be the delegate call. If you want to ensure it is pure, then you would just need to mark the delegate as pure as well. If you try and access a global you'd still get a compile error. It'd be no different than what you can already do today with lazy. Just, it would actually error if the calling function was marked pure. https://run.dlang.io/is/NNT4EC import std.stdio; __gshared int sp = 0; int impureCall() { sp = 20; return sp; } pure int magic(lazy int value = impureCall()) { return value; } pure void parentCall() { magic(); } void main() { writeln(sp); parentCall(); writeln(sp); } ---------------------------------------------- Now with the suggested proposal: __gshared int sp; pure int foo(int delegate() value) { return value(); } void test1() { foo(() { return sp; }); // ok } pure test2() { foo(() { return sp; }); // error calling impure function foo() foo(() pure { return 0; }); // ok }
Jul 28 2020
next sibling parent Avrina <avrina12309412342 gmail.com> writes:
To add onto this, the problem the new proposal would solve is for 
example ones with opApply.

import std.stdio;

struct Container {
     nothrow  nogc
     int opApply(int delegate(int)  nogc nothrow dg) {
        return dg(0);
     }
}

void main() {
     foreach(a ; Container()) {
         int* d = new int; // error currently
     }
}

Templates aren't exactly a solution, you lose access to auto 
inferring the types in foreach if you do.

Where as, in the current state of DIP1032, it is no more than 
syntax sugar that introduces a breaking change. I am against 
DIP1032 in it's current state adds almost nothing and introduces 
breaking changes.
Jul 28 2020
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
 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.
Jul 29 2020
next sibling parent Dennis <dkorpel gmail.com> writes:
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?
Jul 29 2020
prev sibling parent Avrina <avrina12309412342 gmail.com> writes:
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.
Jul 29 2020
prev sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Tue, Jul 28, 2020 at 06:21:44PM -0700, Walter Bright via Digitalmars-d wrote:
 On 7/28/2020 4:00 PM, H. S. Teoh wrote:
 This is a misunderstanding of what was proposed.  What was proposed
 is that the compiler will treat such a call as if it were no longer
 pure ( safe, nothrow, etc.). I.e., if the caller was marked pure,
 this would trigger a compile error that it cannot call an impure
 function.  If the caller was impure to begin with, then it doesn't
 matter anyway.
Thanks for the clarification. But it still tricks the user into thinking the function is pure, since it says right there it is pure. Pure isn't just an attribute for the compiler, it's for the user as it offers a guarantee about what the interface to a function is.
[...] In that case, what about extending inout to attributes besides const? I don't know what's a good syntax for it, maybe something like inout(pure), for lack of a better idea, to indicate conditional purity? 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. T -- Talk is cheap. Whining is actually free. -- Lars Wirzenius
Jul 28 2020
next sibling parent Mathias LANG <geod24 gmail.com> writes:
On Wednesday, 29 July 2020 at 04:45:07 UTC, H. S. Teoh wrote:
 On Tue, Jul 28, 2020 at 06:21:44PM -0700, Walter Bright via 
 Digitalmars-d wrote:

 In that case, what about extending inout to attributes besides 
 const? I don't know what's a good syntax for it, maybe 
 something like inout(pure), for lack of a better idea, to 
 indicate conditional purity?

 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.


 T
I strongly agree with this (as mentioned in this thread and the Github PR already). I agree with Walter that silently removing attributes is bad. The compiler should error if the body tries to do something that violates the attributes of the function. What we want is the ability to say "This function is ${QUAL} if this ${CALLABLE} is ${QUAL}" where QUAL is one or more attributes among (pure, safe,nothrow, nogc) and CALLABLE is a delegate or a function pointer.
Jul 28 2020
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 7/28/2020 9:45 PM, H. S. Teoh wrote:
 In that case, what about extending inout to attributes besides const?
Because people find inout confusing, and this would just make that worse.
 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).
Jul 29 2020
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
Jul 29 2020
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
Jul 29 2020
parent Mathias LANG <geod24 gmail.com> writes:
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.
Jul 29 2020
prev sibling parent reply Dennis <dkorpel gmail.com> writes:
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.
Jul 29 2020
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
Jul 29 2020
next sibling parent reply Mathias LANG <geod24 gmail.com> writes:
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.
Jul 29 2020
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
Jul 29 2020
parent Mathias LANG <geod24 gmail.com> writes:
On Thursday, 30 July 2020 at 06:08:07 UTC, Walter Bright wrote:
 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.
Object.toString is just an example. The problem is that sometimes, you can't template. Like when using classes. So the inherent problem is, how do we compose attributes and non-template-able code ? Note that if you are using templates, DIP1032 has little benefit either, because you can just template the delegate so the attributes are infered.
Jul 30 2020
prev sibling next sibling parent Dennis <dkorpel gmail.com> writes:
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.
That will not work here, opApply is a virtual function. It also breaks in cases where the parameter type is not specified: ``` new Tree().opApply(x => 0); // works with delegate // with template: opApply cannot deduce function from argument types !()(void) ``` This is not a hypothetical situation, I had to deal with this when making std.bigint safe, because a project on buildkite broke. https://github.com/dlang/phobos/blob/f5197c82e7b0d1fad2f37233a72f46a56f3f17eb/std/bigint.d#L1330 Then there's other scenarios where you can't use templates: - Library is compiled as a shared lib - Library is closed source - Library has extern(System) interface (e.g. https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumdisplaymonitors)
Jul 30 2020
prev sibling parent Avrina <avrina12309412342 gmail.com> writes:
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.
Did you even try his example code with templates? Or do you just give an answer without even trying to see if it will work? Sometimes it feels like you only program in D using a feature set that is more similar to C and you aren't aware of the actual problems with some of D features (cause you don't use them). Not surprising opApply() isn't used once in DMD's source. Now sure DMD came from C, even though it's been written in D for quite a while. I searched the commits you have made in phobos. You've made a total of 6 commits that contain *changes* to a line that contained opApply. One was just a tab->spaces whitespace fix. Three were just changing attributes of existing opApply functions eg adding `scope`. One was just a comment change. There wasn't a single commit where you used opApply on your own. So I'm not surprised you don't understand the problem. You probably literally have never used opApply, so obviously you have no awareness of the problems actual users have with the features involved.
Jul 31 2020
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 2020
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 2020
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 2020
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 2020
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 2020
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 2020
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 2020
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 2020
prev sibling parent reply aberba <karabutaworld 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
I think its a good proposal. Now about making the user of a library being able to decide whether to make a delegate pure or impure (as an example), I believe you the library author have ALREADY decided to make it strict so the delegate should also inherit such restrictiveness...most likely the ideal way to use it...as you intended. But if its so that both restrictive and no-restrictive uses are possible as is (without extra work), then I think you have to weigh on which one is more ideal...which in this case is the restrictive. It's more like typed vs weak typed languages. Desiring weak type behavior comes with extra work in a typed language. So having to alias the delegate is the less ideal use case (so its at a disadvantage to require extra work, an alias)... a little price to pay. Plus I personally don't see it as a big deal considering its not a dominant/common requirement and even it was a common case, you the library author has already chosen a desired sane default (doesn't matter the reason)...it is what it is. Now do we have to explain to the user why an impure (as an example) delegate needs to be aliased? Probably yes. Can we not do that with a compiler error message?
Jul 29 2020
parent Mike Parker <aldacron gmail.com> writes:
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!
Jul 29 2020