www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - =?UTF-8?B?ROKAmXM=?= delegates =?UTF-8?B?4oCU?= The good, the bad, and

reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
First and foremost, D’s delegates are a great idea. I worked with 
C++’s member function pointers; they’re awful in syntax and the 
concept is fine, delegates are just better.

D’s delegates have a few issues, some are outright bugs and 
others are improvements that I wonder why they’re not in the 
language.



It’s weird, but it’s true.

One issue is that `const` and `immutable` currently don’t extend 
to the delegate’s context. This is a bug when it comes to the 
intention behind `const` and `immutable` being transitive:
```d
const(void delegate()) dg = &obj.method;
```
This `dg` should not be callable: Its context may not be changed 
through a reference obtained by `dg` (as it is declared a `const` 
variable), but there’s no guarantee that `dg` won’t do that: 
`method` need not be annotated `const`.
```d
const(void delegate() const) dg = &obj.constMethod;
```
This `dg` has a `const` annotation. It promises not to mutate its 
context; therefore, it can be called. If `constMethod` is not 
annotated `const` (or `immutable`), the assignment won’t work.



Because a delegate is a tightly bound context–function pair, a 
delegate annotated `immutable` should implicitly convert to a 
delegate annotated mutable: The re-annotation does not change the 
fact that the context cannot change (by calling the delegate – 
because the called function simply does not do it – or by other 
means) and a reassignment of the delegate annotated mutable does 
not change that either because the context and the function 
pointer cannot be assigned individually.

Another conversion that should Just Work is function pointer to 
delegate, in fact, because a function pointer has no context, its 
context is `immutable`:
```d
void f()  safe { writeln("Hello"); }

void delegate()  safe immutable dg = &f; // today: error

// Workaround:
void delegate()  safe immutable dg = ()  trusted {
     void delegate()  safe immutable result = null;
     result.funcptr = cast(typeof(result.funcptr)) &f;
     return result;
}();
dg(); // prints "Hello" (as it should)
```

We have the following sequence:
`void function()` → `void delegate() immutable` → `void 
delegate() const` → `void delegate()`

The first is a value conversion, the others are reference 
conversions.
The first conversion works when a lambda is used directly, but 
not when the lambda is assigned to an `auto` variable and then 
passed as an argument to a delegate-type parameter.



This applies to closures. (For address of an object–method pair, 
the method tells the precise qualifiers.) A closure has a 
delegate or function pointer type, and arguably, it should have 
the type with the most guarantees (that’s why it infers 
attributes, for example). But for some reason, closures don’t 
infer type qualifiers.
```d
int x;
auto dg = () => x;
pragma(msg, typeof(dg)); // int delegate() pure nothrow  nogc 
 safe
```
The type isn’t wrong, it’s just lacking: It lacks `const`, since 
`x` is captured by the delegate and the delegate doesn’t mutate 
`x` when it runs. So why isn’t `const` one of its attributes? We 
can ask for `const` explicitly, though:
```d
int x;
auto dg = () const => x;
pragma(msg, typeof(dg)); // int delegate() const pure nothrow 
 nogc  safe
```

Does it do what it promises? No:
```d
int x;
auto dg = () const => x += 1; // Why can I do this??
```
Note that `dg` is `pure`. A 0-parameter `const` `pure` delegate 
cannot affect values:
```d
int x = 0;
assert(x == 0); // passes
auto dg = () const => x += 1; // Why can I do this??
pragma(msg, typeof(dg)); // int delegate() const pure nothrow 
 nogc  safe
dg();
assert(x == 1); // passes, but could fail due to optimizations
```

What about `immutable`?
```d
immutable int x;
auto dg = () => x;
pragma(msg, typeof(dg)); // immutable(int) delegate() pure 
nothrow  nogc  safe
```
The `immutable(int)` return type sticks out, but is not the issue 
of concern. The interesting part is that all the things (that is, 
`x`) that `dg` captures are `immutable`. We can ask for 
`immutable` explicitly:
```d
immutable int x;
auto dg = () immutable => x;
pragma(msg, typeof(dg)); // immutable(int) delegate() immutable 
pure nothrow  nogc  safe
```

The inference of `function` instead of `delegate` and the 
inference of `immutable` only make sense if those guarantees can 
be forgotten implicitly.



With the type constructor attributes, one can express that the 
underlying function of a delegate must not mutate its context or 
that the context is outright immutable.

With a type constructor applied to whole delegate type, it can 
(rather: should in some cases) become unusable, which is bad.

What if I want to express that the delegate should not be 
re-assigned? That would mean: The function pointer is `const` or 
`immutable` (doesn’t really matter), but the context is whatever 
it is. I know it’s not *that* useful, but it’s not nothing.

If we imagine a delegate `dg` as a pair `(dg.ptr, dg.funcptr)`, 
it would be as if `dg.funcptr` were `const`, but we leave 
`dg.ptr` (the context) alone. A non-assignable component makes a 
pair non-assignable. Done. Easy. Only the language has no concept 
for it and no syntax either. If you wonder, the syntax could be 
of the shape `int delegate const()`, where `const(int delegate 
const())` is the same as `const(int delegate())`, just like 
`const(const(int)[])` is the same type as `const(int[])`.
Jun 16 2023
next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Friday, 16 June 2023 at 15:29:47 UTC, Quirin Schroll wrote:
 D’s delegates have a few issues, some are outright bugs and 
 others are improvements that I wonder why they’re not in the 
 language.
Excellent writeup. It's nice to have all of these in one place.
 Another conversion that should Just Work is function pointer to 
 delegate, in fact, because a function pointer has no context, 
 its context is `immutable`:
I believe the reason this one doesn't work is that functions and delegates have different calling conventions. In order to allow a function pointer to convert to a delegate, the compiler would have to generate a trampoline.
Jun 17 2023
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 6/17/23 20:37, Paul Backus wrote:
 On Friday, 16 June 2023 at 15:29:47 UTC, Quirin Schroll wrote:
 D’s delegates have a few issues, some are outright bugs and others are 
 improvements that I wonder why they’re not in the language.
Excellent writeup. It's nice to have all of these in one place. ...
Regarding context qualifiers, there is also this issue: https://issues.dlang.org/show_bug.cgi?id=20517
 Another conversion that should Just Work is function pointer to 
 delegate, in fact, because a function pointer has no context, its 
 context is `immutable`:
I believe the reason this one doesn't work is that functions and delegates have different calling conventions. In order to allow a function pointer to convert to a delegate, the compiler would have to generate a trampoline.
There's `std.functional.toDelegate` that generates the trampoline: https://dlang.org/phobos/std_functional.html#toDelegate However, it generates an unqualified context: ```d import std.functional; void foo() safe{} void main(){ void delegate()immutable dg=toDelegate(&foo); // error } ``` (Which it kind of has to because DMD incorrectly rejects the conversion from immutable to unqualified context.)
Jun 17 2023
prev sibling parent Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Saturday, 17 June 2023 at 18:37:48 UTC, Paul Backus wrote:
 On Friday, 16 June 2023 at 15:29:47 UTC, Quirin Schroll wrote:
 Another conversion that should Just Work is function pointer 
 to delegate, in fact, because a function pointer has no 
 context, its context is `immutable`:
I believe the reason this one doesn't work is that functions and delegates have different calling conventions. In order to allow a function pointer to convert to a delegate, the compiler would have to generate a trampoline.
That’s probably the case, but it’s not a good reason. A function taking a `long` isn’t called the same as a function taking an `int` (different sizes), still I can call that function with an `int`; the value can be converted. Having to do an explicit conversion would be annoying and serve no purpose. The same is true for `function` → `delegate`. And there isn’t even an explicit conversion the likes of `cast(DelegateType) fp`.
Jun 20 2023
prev sibling next sibling parent claptrap <clap trap.com> writes:
On Friday, 16 June 2023 at 15:29:47 UTC, Quirin Schroll wrote:
 First and foremost, D’s delegates are a great idea. I worked 
 with C++’s member function pointers; they’re awful in syntax 
 and the concept is fine, delegates are just better.
Somewhat of an aside but every time people talk about function signatures i cant help having a mental image of 20 people trying to squeeze through a single doorway. There's so much to express on function signatures it feels like 90% of the complexity of the language is right there on the entry point to a function. constness, safe, system, nogcn, nothrow, lifetimes, refness, auto this or that, return on parameters, or after the function. then you add this with delegates and how they relate to what they are being passed to or called with. https://www.youtube.com/watch?v=M68GeL8PafE
Jun 17 2023
prev sibling parent reply Atila Neves <atila.neves gmail.com> writes:
On Friday, 16 June 2023 at 15:29:47 UTC, Quirin Schroll wrote:
 First and foremost, D’s delegates are a great idea. I worked 
 with C++’s member function pointers; they’re awful in syntax 
 and the concept is fine, delegates are just better.

 [...]
Are there issues in bugzilla for these?
Jun 29 2023
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 6/29/23 16:52, Atila Neves wrote:
 On Friday, 16 June 2023 at 15:29:47 UTC, Quirin Schroll wrote:
 First and foremost, D’s delegates are a great idea. I worked with 
 C++’s member function pointers; they’re awful in syntax and the 
 concept is fine, delegates are just better.

 [...]
Are there issues in bugzilla for these?
Yes, e.g.: https://issues.dlang.org/show_bug.cgi?id=9149 Though there are also other somewhat egregious issues related to delegates, e.g.: https://issues.dlang.org/show_bug.cgi?id=23136 https://issues.dlang.org/show_bug.cgi?id=22135
Jun 29 2023