Discussion Thread: DIP 1041--Attributes for Higher-Order Functions--Community Review Round 1
Timon Gehr
timon.gehr at gmx.ch
Mon Apr 12 17:21:47 UTC 2021
On 12.04.21 16:44, Q. Schroll wrote:
>
> On Monday, 12 April 2021 at 11:05:14 UTC, Timon Gehr wrote:
>> On 12.04.21 11:38, Mike Parker wrote:
>>>
https://github.com/dlang/DIPs/blob/11fcb0f79ce7ec209fb2a302d1371722d0c8ad82/DIPs/DIP1041.md
...
>>
>> Unfortunately, it is not written too well: The reader gets flooded
with details way before being told what the problem actually is or how
the proposal addresses it.
>
> Doesn't the Abstract explain what the problem is and give a general
idea how it is addressed?
> ...
It does not. It's generic fluff. It's only marginally more explicit
than: "there are problems and to address them we should change the
language".
>> As far as I can tell, this is trying to introduce attribute
polymorphism without actually adding polymorphism, much like `inout`
attempted and ultimately failed to do. I am very skeptical. It's taking
a simple problem with a simple solution and addressing it using an
overengineered non-orthogonal mess in the hopes of not having to add
additional syntax.
>
> You're mistaken. You can take a look at the Alternatives for
seemingly simple solutions. There ain't any.
I know there are, and I literally state how to do it in the quoted excerpt.
> Because D isn't an immutable-all-the-way-down language like e.g. Haskell,
Haskell has plenty of support for mutable data.
> none of the easy solutions are sound. You always have the problem of
assigning the parameter in the functional unless it's `const` or another
flavor of non-mutable.
Assignments are not the problem, it's the inconsistent interpretation of
types using incompatible, special-cased meanings.
> If you don't go the `const` route, you have to deal with assignments
to the parameter before it's called. You have to disallow assignments
that, looking at the types, are a 1-to-1 assignment. IMO, going via
`const` is far more intuitive.
> ...
It's a bad, non-orthogonal solution building on a compiler bug.
> In fact, "not having to add additional syntax" was never the
motivation for the proposal. Not having to introduce attributes
_specific_ to higher-order function was.
> ...
It adds higher-order specific rules to existing attributes without a
good reason, which is a lot worse.
>> To add insult to injury, the first example that's shown in the DIP
as motivation abuses an existing type system hole.
>
> I disagree that it is a hole in the type system.
You are wrong, and I am not sure how to make that point to you. (When I
tried last time, you just claimed that some other well-documented
intentionally-designed feature, like attribute transitivity, is actually
a bug.)
> When having `qual₁(R delegate(Ps) qual₂)` where `qual₁` and `qual₂`
are type qualifiers (`const`, `immutable`, etc.) it is practically most
useful if `qual₁` only applies to the function pointer and (the
outermost layer of) the context pointer while `qual₂` refers to the
property of the context itself.
That allows building a gadget to completely bypass transitivity of
qualifiers, including `immutable` and `shared`.
It's completely unsound, e.g., it allows creating race conditions in
`@safe` code.
You can't say qualifiers are transitive except in this one case, that
translates to them not being transitive at all. A lot of D's type system
design is built on qualifiers being transitive.
> Since the language gives no non-UB way to assign the function pointer
and the context pointer separately, it is not unsound.
>
[Here](https://forum.dlang.org/post/gauloixsonnnlswhbiqe@forum.dlang.org) is
the space for discussing this issue. Unfortunately, it's mostly us two
that care.
>
The obfuscated DIP is unfortunately not helping with that.
In any case, this is now the place to discuss this issue as you have
chosen to use this bug as a basis for evolving the language.
>> `toString` is `const`, `sink` is `const`, the only reference to
result accessible to `toString` is in the context of `sink`, but somehow
`result` is mutated anyway.
>
> See the paragraph above.
>
>> Unsoundness should be fixed, not embraced!
>
> Yes, I guess no one disagrees on that one, but on the question of it
being an instance of it.
>
>> Finally, there's this concern: The DIP assumes that the only
reasonable way to manipulate delegates in higher-order functions
involves calling them, but this is not accurate.
>
> It assumes that the the most common use-case of non-mutable delegate
parameters is only calling them. Returning them is another, but a rarer
one. The DIP details that in this case, the author of the `compose`
function needs to remember not to make the parameters mutable.
> ...
I guess you mean the other way around.
>> ```D
>> auto compose(A,B,C)(C delegate(B) f, B delegate(A) g)pure{
>> return a=>f(g(a));
>> }
>> ```
>>
>> With the proposed changes, composing impure functions suddenly
becomes an impure operation as soon as you abstract it into a
higher-order function. This is pure nonsense. If you have a `pure`
expression and abstract it into a `pure` function, it should not become
less `pure` in the process!
>
> You did it correctly in the sense of the DIP. `compose` takes `f` and
`g` as mutable. None of the proposed changes apply to mutable delegate
parameters.
Fair, but that's a technicality tangential to my point, and as you may
have been able to tell, I have certain reservations about reusing
`const` in this fashion.
> By the changes proposed by this DIP, `compose` is `pure`. However,
all delegates you pass to it lose information of attributes because you
_could_ assign `f` or `g` in `compose`, no problem.
> ...
But that's a terrible reason to not be able to annotate them `const`.
`const` means "this won't change", it does not mean "if you compose
this, it won't be recognized as `pure`" and there is no clear way to get
from one to the other. It's a textbook example of a messy non-orthogonal
design that does not make any sense upon closer inspection.
> But as you don't intend to mutate `f` or `g` in it, you could get the
idea of making them `const` like this:
Yes, let's assume that was my intention.
> ```D
> C delegate(A) compose(A, B, C)(const C delegate(B) f, const B
delegate(A) g) pure
> {
> return a => f(g(a));
> }
> ```
> Then, by the proposed changes, only `pure` arguments lead to a `pure`
call expression.
Which was my point. This is indefensible.
> However, `compose` is a good example why this is not an issue: It is
already a template. Why not go the full route and make the `delegate`
part of the template type arguments like this:
> ```D
> auto compose(F : C delegate(B), G : B delegate(A), A, B, C)(F f, G g)
pure
> {
> return delegate C(A arg) => f(g(arg));
> }
> ```
The fact that there is some ugly workaround for my illustrative example
that also defeats the point of your DIP does not eliminate the problem
with the DIP.
> Unfortunately (see
[here](https://issues.dlang.org/show_bug.cgi?id=21823)), when calling
`compose` with a `const` declared delegate value, D's IFTI infers
`const` for the parameter type although the parameter is copied. I
didn't realize that when writing the DIP. This is a problem, but it can
be fixed (probably should).
(Your reinterpretation of what delegate qualifiers mean would need a DIP
in its own right and it would hopefully be rejected.)
More information about the Digitalmars-d
mailing list