Feedback Thread: DIP 1033--Implicit Conversion of Expressions to Delegates--Final Review
Q. Schroll
qs.il.paperinik at gmail.com
Sun Nov 22 05:16:50 UTC 2020
TL;DR: Fix attributes for functions taking delegates as
parameters first. Otherwise, this DIP is useless with respect to
the direction D is intended to go: using more attributes
(default, inferred or explicit ones).
The DIP says: "Although this DIP renders lazy redundant and
unnecessary, it does not propose actually removing lazy. That
will be deferred for a future DIP." That indicates that replacing
`lazy T` by T delegate() is fine in any case. It's not! When
attributes are present, behavior cannot even easily be replicated
with delegates.
Those two functions should be practically identical, right?
T foo(T)(lazy T value) /*pure*/ { return value(); }
T goo(T)(T delegate() value) /*pure?*/ { return value(); }
If T isn't something really funny that might be impure when
moved, foo will be inferred `pure` always. Noticed the question
mark I put behind goo's pure? Well, goo will be inferred `pure`
never because the compiler sees its body calling the impure
delegate.
So from the point of attributes, `lazy` and delegates are vastly
different. Attribute inference is near useless for templates that
take delegate parameters. Attributes have a hard time being
applied to regular functions taking delegate parameters.
The programmer is stuck between a rock and a hard place:
A) Put the desired attribute(s) on the delegate
B) Overload on pure and impure delegates.
C) Ignore attributes (which is what most would do, accidentally).
Issues:
A) Makes the function not available for closures that aren't
complying to the attribute.
B) For if the function can be inferred pure, @safe, nothrow
and/or @nogc, make up to 16 overloads, all with the same body. No
one wants that, not even with mixins.
C) But then: why have attributes in the first place
I saw myself giving up D because I thought attributes are one of
the best things D has to offer, but failing it so badly is sad.
Completely idiomatic @safe code must drop @safe because **one**
library function just works best using a delegate (or function
pointer).
The DIP introduces a questionable implicit conversion of T to T
delegate() and questionable distinctions between function
pointers and delegates on usage, but misses the point of
attributes completely. Solving the attribute issue could make up
the drawbacks of the implicit conversion.
That's for the problem.
---
But I have an idea for a solution.
Assuming a delegate parameter is called in the body of the
function it's the parameter of, which is---in my opinion---a very
reasonable assumption, it should be the caller who's deciding
what behavior of a function is fine and which isn't. `lazy` works
like that.
S pureFactory() pure;
S impureFactory();
void goo(S delegate() value) pure { return value(); }
should compile. Even without the body visible. Here's the reason:
void pureContext() pure
{
goo(&pureFactory); // compiles (1)
goo(&impureFactory); // error (2)
}
(1) Plugging in a pure delegate in an otherwise pure function
will result in a pure operation, so the context (= the attributes
of the caller) is satisfied.
(2) Plugging in an impure delegate in an otherwise pure function
will result in a possibly impure operation, so the context is not
satisfied.
void impureContext()
{
goo(&pureFactory); // compiles (3)
goo(&impureFactory); // compiles (4)
}
(3) See (1). Pure operations are allowed in an impure context.
(4) Plugging in an impure delegate in an otherwise pure function
will result in a possibly impure operation, but the context does
not care.
Type checking goo isn't complicated. The compiler just needs to
pretend that the delegate parameter type is `S delegate() pure`
(i.e. the original `S delegate()` with goo's `pure` attribute).
If that is fine, goo is pure relative to its parameter.
The caller can decide on the callee's signature, the parameter's
(delegate) type, and its own attributes whether a call will be
valid. This approach does not need complicated analysis by the
compiler. The question "Am I plugging in a pure delegate argument
to a (relatively) pure (i.e. pure annotated) function?" is
answered by signatures and types alone.
It's similar to weakly pure functions. Allowing weakly pure
functions made more functions strictly pure. The same way,
relatively pure functions make more functions non-relatively pure.
I have not thought in depth about nested delegate parameter
types, i.e. functions like
void f(int delegate(int delegate()) higherOrderParam) pure {
... }
but solving it for the first layer will solve most applications
of delegates. It seems easy, but no one so far has affirmed me
the following reasoning is correct in general:
int g() pure;
int h();
In the body of f,
higherOrderParam(&g); // compiles
higherOrderParam(&h); // error
since higherOrderParam might call its parameter, but f knows what
it's plugging in. So I assume that delegate types need never be
annotated pure/whatever since the context will always be able to
discern if the thing it's concerned about will be pure/whatever
or not.
The only drawbacks of my solution that I know of are
* pure/whatever functions called with impure delegates that
aren't called become assumed impure (breaking change in theory)
* pure/whatever functions aren't guaranteed to act pure/whatever
unless all argumetns to delegate parameters are pure/whatever.
This is a non-issue for all functions without delegate parameters
and the correct thing for almost all functions with delegate
parameters.
* Template functions could be surprisingly inferred pure/whatever
when template parameters are delegate or function pointer types.
Surprising here means to the programmer; almost guaranteed to be
a pleasant surprise.
The breaking of the change can be seen to be rather weak since
plugging in an impure delegate in a pure function will still be
in a greater context where after all, that delegate will be
called. Delegates are to be called after all.
With that change, attributes on delegate parameter mean: This
function cannot work properly if that delegate isn't
pure/whatever.
More information about the Digitalmars-d
mailing list