July 28, 2020
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.
July 28, 2020
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:
July 28, 2020
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.
July 29, 2020
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
}


July 29, 2020
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.
July 28, 2020
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
July 29, 2020
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.
July 29, 2020
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).
July 29, 2020
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.
July 29, 2020
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?