December 09
On Monday, 9 December 2024 at 06:47:26 UTC, Manu wrote:
>
> Right. Do you have examples of this from other languages and how it's expressed? Lifetime's in Rust are the obvious benchmark, but I don't see any evidence that D has a taste for this sort of thing.


Nim uses an `effectsOf` annotation, see https://nim-lang.org/docs/manual.html#effect-system-effectsof-annotation

That said, IME effect systems are not worth it and the complexity budget is better spent elsewhere.
December 09
On 09/12/2024 8:00 PM, Araq wrote:
> On Monday, 9 December 2024 at 06:47:26 UTC, Manu wrote:
>>
>> Right. Do you have examples of this from other languages and how it's expressed? Lifetime's in Rust are the obvious benchmark, but I don't see any evidence that D has a taste for this sort of thing.
> 
> 
> Nim uses an `effectsOf` annotation, see https://nim-lang.org/docs/ manual.html#effect-system-effectsof-annotation
> 
> That said, IME effect systems are not worth it and the complexity budget is better spent elsewhere.

I've been thinking of an effect system for D, specifically for improvements to shared.

However, I'm not seeing the benefits for this.

For @safe @nogc these are boolean, whereas nothrow is a set, pure is an effect. Then there is type qualifiers const, immutable and shared.

Its not one problem, its more like three that we'd like to solve here.

December 10
On Monday, 9 December 2024 at 06:57:31 UTC, Manu wrote:
>
> Interesting idea. It seems like a novel solution. Approaching it from this perspective feels hard to reason about though... it feels like it would add serious friction to introspection activities?

How so? Nothing is different in terms of introspection. We already have friction on how to tell if a call is viable. Other than that, I don't see a difference here.

-Steve
December 10
On Sunday, 8 December 2024 at 05:54:40 UTC, Manu wrote:
> I've been trying out an API strategy to use sink functions a lot more to hoist the item allocation/management logic to the user and out from the work routines. This idea has received a lot of attention, and I heard a lot of people saying this was a planned direction to move in phobos and friends.
>
> [...]

I remember Mathias had a plan for this. I think.
December 10
On 12/9/24 08:00, Araq wrote:
> On Monday, 9 December 2024 at 06:47:26 UTC, Manu wrote:
>>
>> Right. Do you have examples of this from other languages and how it's expressed? Lifetime's in Rust are the obvious benchmark, but I don't see any evidence that D has a taste for this sort of thing.
> 
> 
> Nim uses an `effectsOf` annotation, see https://nim-lang.org/docs/ manual.html#effect-system-effectsof-annotation
> ...

Does this only work if the callback is directly a parameter?

> That said, IME effect systems are not worth it and the complexity budget is better spent elsewhere.

Well, I think given that we do any kind of parametric polymorphism, effect polymorphism is not a big leap anymore.

But sure, another way to get out of this conundrum would be to remove the function effect annotations completely, though I think some people are getting some mileage out of them and would like to be able to use them properly with callbacks instead.
December 10
On 12/9/24 07:47, Manu wrote:
> On Mon, 9 Dec 2024 at 01:51, Timon Gehr via Digitalmars-d <digitalmars- d@puremagic.com <mailto:digitalmars-d@puremagic.com>> wrote:
> 
>     On 12/8/24 06:54, Manu wrote:
>      > Here's the stupidest idea ever: expand inout to take an argument...
>      >
>      > void parseThings(string s, void delegate(Thing) inout(nothrow)
>      > inout(@nogc) sink) inout(nothrow) inout(@nogc)
>      > {
>      >    //...
>      >    sink(Thing());
>      >    //...
>      > }
>      > ...
> 
>     Issue with this is you will still not know which `inout`s match up.
>     This
>     is already an issue with the existing `inout`, which just arbitrarily
>     selects a convention. In particular, it does not really work with
>     higher-order functions the way you'd need it to here.
> 
> 
> Yes, this is obviously an issue, and we have it in D comprehensively; it's the main issue with our 'safe' stuff too; where rust has explicit lifetime attribution... it's essentially the same problem all round; it needs tags that specify things which are associated.
> In lieu of that though, I would say, if there are multiple things marked inout(...) in the way I proposed, you would assume the most pessimistic from the set of possibilities is supplied.
> 
> void inheritAttribs(string s, void delegate() inout(nothrow) inout(@nogc) fun_1, void delegate() inout(nothrow) inout(@nogc) fun_2) inout(nothrow) inout(@nogc)
> {
>    //...
>    fun_1();
>    fun_2();
>    //...
> }
> 
> In this case, `inheritAttribs` would only be nothrow in the event BOTH fun_1 and fun_2 are nothrow, likewise for nogc...
> ...

Well, this is one limitation, but the `inout` on the delegate does not even match up with the `inout` on `inheritAttribs` those are already different.

This does not compile;

```d
inout(int)* foo(inout(int)* delegate()inout dg,inout(int)* x){
    return x;
}

void main(){
    const(int)* delegate()const dg;
    const(int)* x;
    foo(dg,x); // error
}
```

It seems at least this would need to work.

`inout` is very confusing because different `inout` annotations can get conflated or not conflated. It's also the main reason why the implementation is unsound, it's not done consistently during type checking.

> 
>      > Surely people have had better ideas? But you get the point, and
>     this is
>      > basically essential to make the 'sink' pattern work at all in D.
>      >
>      > No, no templates; it's not right to generate multiple copies of
>     identical functions just because something it CALLS would transfer
>     an attribute. The function itself is identical no matter the state
>     of any attribute transference, and so this isn't a template problem;
>     it's a pattern matching problem.
> 
>     Well, it can be seen as a homogeneous vs heterogeneous compilation
>     problem. The attributes can still act a bit like template parameters,
>     but only one instance must be created that works for all of them. It's
>     sometimes called parametric polymorphism.
> 
> 
> Right. Do you have examples of this from other languages and how it's expressed? Lifetime's in Rust are the obvious benchmark, but I don't see any evidence that D has a taste for this sort of thing.
> ...

Well, Java, C#, Scala support homogeneous compilation in their generics.

In Haskell everything is polymorphic by default with implicit universal quantification over lower-case type parameters.

> I do think that inout could work for all attributes the same as inout does for const (with the same limitations).

Well, as I showed above, those limitations are kind of fatal for your use case.

> You could see `inout` as shorthand for `inout(const)` under my suggestion.
> Obviously it must enforce the most restrictive implementation inside the code; my function `inheritAttribs` above must be nothrow and nogc internally, but externally it would allow a mapping from one of the 'input' attributes to the 'output' attribute in a non-templated way.

`inout` also interacts with `immutable`, not only `const`.

December 10

On Sunday, 8 December 2024 at 05:54:40 UTC, Manu wrote:

>

We have a serious problem though; a function that receives a sink function
must transfer the attributes from the sink function to itself, and we have
no language to do that...
What are the leading strategies that have been discussed to date? Is there
a general 'plan'?

This was brought up in a thread in DIP Ideas. I contributed my idiosyncratic thoughts here:

https://forum.dlang.org/post/rtvjsdyuqwmzwiggsolw@forum.dlang.org

Suggestion #2 in that post addresses the issue you raise. In short, I proposed that the language default to inheriting attributes for the enclosing function at the call site, and then suggested a (callable function/delegate) parameter keyword @noimply for those rare cases when you don't want the function to inherit the characteristics of the delegate that you pass. I tried to explain it in the post.

All my post did was suggest some hopefully elegant syntax for a number of attribute-related issues, of which this was one.

December 10
On 12/8/24 20:19, Steven Schveighoffer wrote:
> On Sunday, 8 December 2024 at 05:54:40 UTC, Manu wrote:
>> I've been trying out an API strategy to use sink functions a lot more to hoist the item allocation/management logic to the user and out from the work routines. This idea has received a lot of attention, and I heard a lot of people saying this was a planned direction to move in phobos and friends.
>>
>> ```d
>> void parseThings(string s, void delegate(Thing) sink)
>> {
>>   //...
>>   sink(Thing());
>>   //...
>> }
>> ```
>>
>> We have a serious problem though; a function that receives a sink function
>> must transfer the attributes from the sink function to itself, and we have
>> no language to do that...
>> What are the leading strategies that have been discussed to date? Is there
>> a general 'plan'?
> 
> I've proposed something like the following in the past, but I don't know how much appetite there is for it:
> 
> ```d
> // assuming Thing() is @nogc nothrow @safe, but not pure
> void parseThings(string s, @called void delegate(Thing) sink) @nogc nothrow @safe
> {
>     sink(Thing());
> }
> ```
> ...

Well, this is more sane than the "contract invalidation" proposal in that it is explicit, does not change the meaning of existing code, and allows somewhat more composition patterns. It's also forward-compatible with a proper solution, while most likely remaining more convenient for the cases where it is sufficient.

So maybe it's worth pursuing to stop some of the bleeding, but this will run into its own limitations very quickly.

In terms of implementation, it's probably a bit tricky to catch every place where the compiler assumes that e.g. `@safe` actually means `@safe`. And there are also traits like `isSafe` that would most likely subtly change meaning.
December 11
On 11/12/2024 11:45 AM, Timon Gehr wrote:
> Well, this is more sane than the "contract invalidation" proposal in that it is explicit, does not change the meaning of existing code, and allows somewhat more composition patterns. It's also forward-compatible with a proper solution, while most likely remaining more convenient for the cases where it is sufficient.
> 
> So maybe it's worth pursuing to stop some of the bleeding, but this will run into its own limitations very quickly.

Contract invalidation can be explicit with the help of an attribute and I'm fine with any argument supporting its need as I agree its probably the better way to go.

But, it is a quick and dirty solution that doesn't solve the underlying problem of type qualifiers and attributes invalidation based upon the call.

What it does do is solve ``opApply``.

This proposal of Steven's is pretty much identical in nature.

Unfortunately only you seem to have any understanding of what a solution could look like and I've been failing to make any head way on it.

December 12
On Tue, 10 Dec 2024 at 12:26, Steven Schveighoffer via Digitalmars-d < digitalmars-d@puremagic.com> wrote:

> On Monday, 9 December 2024 at 06:57:31 UTC, Manu wrote:
> >
> > Interesting idea. It seems like a novel solution. Approaching it from this perspective feels hard to reason about though... it feels like it would add serious friction to introspection activities?
>
> How so? Nothing is different in terms of introspection. We already have friction on how to tell if a call is viable. Other than that, I don't see a difference here.
>
> -Steve
>

I think the essence of the issues I imagine stem from the fact that
`parseThings` takes on different characteristics (argument attributes
change) when/*where* it's CALLED. It means you can't ask hard facts about
the type; instead you'd be forced to use more __traits(compiles)-like
evaluations to test for compatibility; and I think those are a really bad
pattern in general.
The difference to `inout`; is that it's reliable and detectible at compile
time; it assumes the most restrictive traits, because it *could* be called
that way... your suggestion seems to be the opposite of that; permissive at
compile time, and I can imagine (by guy feeling) that leading to a world of
trouble.
What I suggest with `inout(attrib)` would do the same thing as inout(const)
does today; assume the most restrictive thing at compile time so that it's
a valid call in all situations.