Thread overview
Multiple delegates, contextual delegates
Jun 24, 2019
Aphex
Jun 26, 2019
angel
Jun 26, 2019
Aphex
Jun 26, 2019
XavierAP
Jun 26, 2019
Aphex
Jun 26, 2019
XavierAP
June 24, 2019
I believe it would really help D to have multiple delegates and contextual delegates.

Take, for example

alias Caller = void delegate();

class X
{
    Caller Do;
}


x.Do = () { };


The issue is that one cannot assign multiple functions to Do and easily call them all in sequence(or even parallel using tasks).

I have created a library solution but the problem is that I have to keep the library around. I believe it is a very useful tool to be able to contain multiple delegates in a single object reference because there are many applications where one wants too allow multiple behaviors attached to a single reference. E.g., it has applications in messaging, notifications, graphics, systems, etc.

It's true one can simply make an array of callbacks, but then one still has to manually fire them all, deal with nulls, etc.


Ideally something like

alias Caller = void mdelegate();

class X
{
    Caller Do;
}

x.Do = () { writeln("1"); };

x.Do = () { writeln("2"); };

x.Do = () { writeln("3"); };

x.Do();

then fires all the delegates(in sequence of course).  (One could use ~= or += for adding and -= for removing if one wants)

It is true that this can somewhat be effectively done in a library but it should be part of the standard library and implemented well. I think though that it is better part of the language itself because it is quite useful.

One could have different ways to fire the delegate such as sequential iterative(sorta emulating yield) or parallel(creates a task for each one).

By having mdelegate one can immediately convert a delegate in to a mutlidelegate by a search and replace of the term without any issues or modification of other code, except if = is used which might cause problems on reassignment. It may require slight modifications on assignment to make things work but a mdelegate can behave as a normal delegate otherwise.


Contextual delegates are simply delegates that are methods that also capture the context.




alias Caller = void cdelegate();

class X
{
    int z = 3;
    Caller Do;
}

int z = 4;
int y = 10;
x.Do = () { writeln(context.z*y); }; // context used to disambiguate and reference object(possibly could use this but a this may already exist)

x.Do();


Essentially it keeps two "this"'es. The this of the object also of the context.

It's true one can do this as


alias Caller = void cdelegate(X);

class X
{
    int z = 3;
    Caller Do;
}

int y = 10;
x.Do = (q) { writeln(q.z*y); };

x.Do(x);

It might be better called member delegates as they are hybrids of members and delegates.


While it doesn't seem like much work to create them in d, it is quite ugly in having to pass the object.


By having a cdelegate one immediately can convert delegates used in structs and classes in to more powerful objects with no other code change than a search and replace. Without it one has to find all occurrence of and modify it to work.



Of course, then one has cmdelegate! ;) A contextual multi delegate! Isn't generalization fun?

These features really take very little compiler magic. Multi delegates can be done in a library but contextual delegates cannot.  Both extend delegates very easily and without much trouble yet make them more powerful. They also can be combined to multiply the usefulness.

In fact, it may be possible to simply extend the above behavior to the keyword delegate without any ill-effect. It would be 100% backwards compatible. The only issue is dealing with the assignment of delegates since previous behavior is expected to overwrite while new behavior would be expected to append... and these are mutually exclusive. To keep things 100% backwards compatible would require assignment to overwrite which would turn a multi-delegate in to a single delegate... and appendage would turn a delegate in to a multi-delegate.

e.g.,

x.Do = () { }; // First, and so old school delegate.
x.Do = () { }; // Overwrites.
x.Do += () { }; // Added a new delegate and now we have a multi-delegate with 2 delegates

x.Do = () { }; // Back to a single delegate that overwrote the previous two.
x.Do += () { }; // Added a new delegate and now we have a multi-delegate with 2 delegates
x.Do -= () { }; // Back to a single delegate since we removed the old one(technically not since the reference must be correct)


So the only issue here is that using multi-delegates in pre-existing code may fail if one does not know about future assignment and the multi-delegates get overwritten. This generally won't be a problem because rarely does one assign to a delegate multiple times, but even so, it's just a matter of converting to the appender op.

The power these offer far outweigh the rare likelihood of unexpected behavior(one far more likely to have other more serious issues such as a typical buffer overflow).



What do you think?






June 26, 2019
On Monday, 24 June 2019 at 16:05:33 UTC, Aphex wrote:
> I believe it would really help D to have multiple delegates and contextual delegates.
>
> Take, for example
>
> alias Caller = void delegate();
>
> class X
> {
>     Caller Do;
> }
>
>
> x.Do = () { };
>
>
> The issue is that one cannot assign multiple functions to Do and easily call them all in sequence(or even parallel using tasks).
>
> I have created a library solution but the problem is that I have to keep the library around. I believe it is a very useful tool to be able to contain multiple delegates in a single object reference because there are many applications where one wants too allow multiple behaviors attached to a single reference. E.g., it has applications in messaging, notifications, graphics, systems, etc.
>
> It's true one can simply make an array of callbacks, but then one still has to manually fire them all, deal with nulls, etc.
>
>
> Ideally something like
>
> alias Caller = void mdelegate();
>
> class X
> {
>     Caller Do;
> }
>
> x.Do = () { writeln("1"); };
>
> x.Do = () { writeln("2"); };
>
> x.Do = () { writeln("3"); };
>
> x.Do();
>
> then fires all the delegates(in sequence of course).  (One could use ~= or += for adding and -= for removing if one wants)
>
> It is true that this can somewhat be effectively done in a library but it should be part of the standard library and implemented well. I think though that it is better part of the language itself because it is quite useful.
>
> One could have different ways to fire the delegate such as sequential iterative(sorta emulating yield) or parallel(creates a task for each one).
>
> By having mdelegate one can immediately convert a delegate in to a mutlidelegate by a search and replace of the term without any issues or modification of other code, except if = is used which might cause problems on reassignment. It may require slight modifications on assignment to make things work but a mdelegate can behave as a normal delegate otherwise.
>
>
> Contextual delegates are simply delegates that are methods that also capture the context.
>
>
>
>
> alias Caller = void cdelegate();
>
> class X
> {
>     int z = 3;
>     Caller Do;
> }
>
> int z = 4;
> int y = 10;
> x.Do = () { writeln(context.z*y); }; // context used to disambiguate and reference object(possibly could use this but a this may already exist)
>
> x.Do();
>
>
> Essentially it keeps two "this"'es. The this of the object also of the context.
>
> It's true one can do this as
>
>
> alias Caller = void cdelegate(X);
>
> class X
> {
>     int z = 3;
>     Caller Do;
> }
>
> int y = 10;
> x.Do = (q) { writeln(q.z*y); };
>
> x.Do(x);
>
> It might be better called member delegates as they are hybrids of members and delegates.
>
>
> While it doesn't seem like much work to create them in d, it is quite ugly in having to pass the object.
>
>
> By having a cdelegate one immediately can convert delegates used in structs and classes in to more powerful objects with no other code change than a search and replace. Without it one has to find all occurrence of and modify it to work.
>
>
>
> Of course, then one has cmdelegate! ;) A contextual multi delegate! Isn't generalization fun?
>
> These features really take very little compiler magic. Multi delegates can be done in a library but contextual delegates cannot.  Both extend delegates very easily and without much trouble yet make them more powerful. They also can be combined to multiply the usefulness.
>
> In fact, it may be possible to simply extend the above behavior to the keyword delegate without any ill-effect. It would be 100% backwards compatible. The only issue is dealing with the assignment of delegates since previous behavior is expected to overwrite while new behavior would be expected to append... and these are mutually exclusive. To keep things 100% backwards compatible would require assignment to overwrite which would turn a multi-delegate in to a single delegate... and appendage would turn a delegate in to a multi-delegate.
>
> e.g.,
>
> x.Do = () { }; // First, and so old school delegate.
> x.Do = () { }; // Overwrites.
> x.Do += () { }; // Added a new delegate and now we have a multi-delegate with 2 delegates
>
> x.Do = () { }; // Back to a single delegate that overwrote the previous two.
> x.Do += () { }; // Added a new delegate and now we have a multi-delegate with 2 delegates
> x.Do -= () { }; // Back to a single delegate since we removed the old one(technically not since the reference must be correct)
>
>
> So the only issue here is that using multi-delegates in pre-existing code may fail if one does not know about future assignment and the multi-delegates get overwritten. This generally won't be a problem because rarely does one assign to a delegate multiple times, but even so, it's just a matter of converting to the appender op.
>
> The power these offer far outweigh the rare likelihood of unexpected behavior(one far more likely to have other more serious issues such as a typical buffer overflow).
>
>
>
> What do you think?


Indeed it looks like a library solution.
Its alleged usefulness has nothing to do with being a library as opposed to being a part of the core language. Normally, the core language implements stuff that is very hard or very ugly to implement in a library, and your proposal will look perfect in a library implementation.

Another question regarding the "-=" operator - a delegate removal.
How can you recognize the delegate that has to be removed ? Do you need to type in the delegate code, just to identify the candidate for removal ?
June 26, 2019
On Wednesday, 26 June 2019 at 09:48:35 UTC, angel wrote:
> On Monday, 24 June 2019 at 16:05:33 UTC, Aphex wrote:
>> I believe it would really help D to have multiple delegates and contextual delegates.
>>
>> Take, for example
>>
>> alias Caller = void delegate();
>>
>> class X
>> {
>>     Caller Do;
>> }
>>
>>
>> x.Do = () { };
>>
>>
>> The issue is that one cannot assign multiple functions to Do and easily call them all in sequence(or even parallel using tasks).
>>
>> I have created a library solution but the problem is that I have to keep the library around. I believe it is a very useful tool to be able to contain multiple delegates in a single object reference because there are many applications where one wants too allow multiple behaviors attached to a single reference. E.g., it has applications in messaging, notifications, graphics, systems, etc.
>>
>> It's true one can simply make an array of callbacks, but then one still has to manually fire them all, deal with nulls, etc.
>>
>>
>> Ideally something like
>>
>> alias Caller = void mdelegate();
>>
>> class X
>> {
>>     Caller Do;
>> }
>>
>> x.Do = () { writeln("1"); };
>>
>> x.Do = () { writeln("2"); };
>>
>> x.Do = () { writeln("3"); };
>>
>> x.Do();
>>
>> then fires all the delegates(in sequence of course).  (One could use ~= or += for adding and -= for removing if one wants)
>>
>> It is true that this can somewhat be effectively done in a library but it should be part of the standard library and implemented well. I think though that it is better part of the language itself because it is quite useful.
>>
>> One could have different ways to fire the delegate such as sequential iterative(sorta emulating yield) or parallel(creates a task for each one).
>>
>> By having mdelegate one can immediately convert a delegate in to a mutlidelegate by a search and replace of the term without any issues or modification of other code, except if = is used which might cause problems on reassignment. It may require slight modifications on assignment to make things work but a mdelegate can behave as a normal delegate otherwise.
>>
>>
>> Contextual delegates are simply delegates that are methods that also capture the context.
>>
>>
>>
>>
>> alias Caller = void cdelegate();
>>
>> class X
>> {
>>     int z = 3;
>>     Caller Do;
>> }
>>
>> int z = 4;
>> int y = 10;
>> x.Do = () { writeln(context.z*y); }; // context used to disambiguate and reference object(possibly could use this but a this may already exist)
>>
>> x.Do();
>>
>>
>> Essentially it keeps two "this"'es. The this of the object also of the context.
>>
>> It's true one can do this as
>>
>>
>> alias Caller = void cdelegate(X);
>>
>> class X
>> {
>>     int z = 3;
>>     Caller Do;
>> }
>>
>> int y = 10;
>> x.Do = (q) { writeln(q.z*y); };
>>
>> x.Do(x);
>>
>> It might be better called member delegates as they are hybrids of members and delegates.
>>
>>
>> While it doesn't seem like much work to create them in d, it is quite ugly in having to pass the object.
>>
>>
>> By having a cdelegate one immediately can convert delegates used in structs and classes in to more powerful objects with no other code change than a search and replace. Without it one has to find all occurrence of and modify it to work.
>>
>>
>>
>> Of course, then one has cmdelegate! ;) A contextual multi delegate! Isn't generalization fun?
>>
>> These features really take very little compiler magic. Multi delegates can be done in a library but contextual delegates cannot.  Both extend delegates very easily and without much trouble yet make them more powerful. They also can be combined to multiply the usefulness.
>>
>> In fact, it may be possible to simply extend the above behavior to the keyword delegate without any ill-effect. It would be 100% backwards compatible. The only issue is dealing with the assignment of delegates since previous behavior is expected to overwrite while new behavior would be expected to append... and these are mutually exclusive. To keep things 100% backwards compatible would require assignment to overwrite which would turn a multi-delegate in to a single delegate... and appendage would turn a delegate in to a multi-delegate.
>>
>> e.g.,
>>
>> x.Do = () { }; // First, and so old school delegate.
>> x.Do = () { }; // Overwrites.
>> x.Do += () { }; // Added a new delegate and now we have a multi-delegate with 2 delegates
>>
>> x.Do = () { }; // Back to a single delegate that overwrote the previous two.
>> x.Do += () { }; // Added a new delegate and now we have a multi-delegate with 2 delegates
>> x.Do -= () { }; // Back to a single delegate since we removed the old one(technically not since the reference must be correct)
>>
>>
>> So the only issue here is that using multi-delegates in pre-existing code may fail if one does not know about future assignment and the multi-delegates get overwritten. This generally won't be a problem because rarely does one assign to a delegate multiple times, but even so, it's just a matter of converting to the appender op.
>>
>> The power these offer far outweigh the rare likelihood of unexpected behavior(one far more likely to have other more serious issues such as a typical buffer overflow).
>>
>>
>>
>> What do you think?
>
>
> Indeed it looks like a library solution.
> Its alleged usefulness has nothing to do with being a library as opposed to being a part of the core language. Normally, the core language implements stuff that is very hard or very ugly to implement in a library, and your proposal will look perfect in a library implementation.

1. The problem with a library solution is that it requires it part of the library. This is not alway effective. It would need to be part of phobos so at least it is used every. Not everything is a candidate for libraries even if they work in libraries. Virtually everything can, in theory, be a library solution. One could have make delegates a library solution, it is possible and functional... but it is too common and it loses functionality.


2. It is a direct extension of something that is already a core semantic in the compiler and since it can be directly extend the keyword `delegate` with being 100% backwards compatible, splitting it in to a library prevents such a useful extension.

3. cdelegate cannot be done in a library(unless there is some trick using D's meta programming). One necessarily has to create redundant code and this is very ugly and error prone.

4. By extending delegate one immediately adds the ability for all programs new and old to take advantage of it it without changing any code. Everything that was setup for single non-contextual delegates will be upgraded to multiple contextual delegates. 99.99% of all previous programs would work, only those that hack delegates in some way *may* break.

Part of the compiler is not just to implement a limited set of functionality but also unify the language in to a cohesive whole. If you put everything in a library you end up created excessive verbosity which complicates coding.


The mantra of pushing everything in to the library gets a bit old. It may make it easier to maintain the compiler and easier on compiler writers but it is not in and off itself optimal.


Think about it like this. Suppose someone wrote a compiler that only used integers as it's number type. E.g., 3, 4, 193, -43, etc...

Now you come along and say "Hey, let's extend the integers to include complex numbers"...

There is no harm because all integers are complex. Effectively all those programs are using complex numbers even if they were written as if they only could use integers.

The only problem is when program hack the internal representation, but which is already unsafe and is problematic in general.

> Another question regarding the "-=" operator - a delegate removal.
> How can you recognize the delegate that has to be removed ? Do you need to type in the delegate code, just to identify the candidate for removal ?

Functions are referenced by their pointer. It is unique, no two functions can refer to the same location unless they are the same. It does require tracking the function but it is not too bad.

Another possible way is to use an associative array:

void delegate() X;

X Callbacks;

Callbacks["Main CB"] = () { ... };
Callbacks.["Main CB"] = null; // removes it

A combination could work

auto z = Callbacks["Main CB"] = () { ... };
Callbacks ~= (auto x = () { ... };);
auto y = Callbacks ~= () { ... };;
Callbacks -= y;
Callbacks -= x;
Callbacks -= z;
Callbacks.["Main CB"] = null; // already removed
Callbacks[y] = null; // already removed

Where essentially the first 250 hash values are reserved for direct index accessing.

Callbacks(); // Fires all delegates in lexicographical order, return first or last or array value?
Parallel!Callbacks() // Fires all delegates in parallel(this could be done in a library).
foreach(c; Callbacks) // Iterates over all the stores callbacks.







June 26, 2019
On Wednesday, 26 June 2019 at 09:48:35 UTC, angel wrote:
>
> Another question regarding the "-=" operator - a delegate removal.
> How can you recognize the delegate that has to be removed ? Do you need to type in the delegate code, just to identify the candidate for removal ?

This proposal looks equivalent to delegate concatenation in C#. In that case -= relies on the == operator. C# delegates are classes inheriting the System.Delegate abstract class (which already provides implementations for all these operatorrs).

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/delegates
June 26, 2019
On Wednesday, 26 June 2019 at 12:16:19 UTC, Aphex wrote:
> On Wednesday, 26 June 2019 at 09:48:35 UTC, angel wrote:
>>
>> Indeed it looks like a library solution.
>> Its alleged usefulness has nothing to do with being a library as opposed to being a part of the core language. Normally, the core language implements stuff that is very hard or very ugly to implement in a library, and your proposal will look perfect in a library implementation.
>
> 1. The problem with a library solution is that it requires it part of the library. This is not alway effective. It would need to be part of phobos so at least it is used every. Not everything is a candidate for libraries even if they work in libraries. Virtually everything can, in theory, be a library solution. One could have make delegates a library solution, it is possible and functional... but it is too common and it loses functionality.
>
>
> 2. It is a direct extension of something that is already a core semantic in the compiler and since it can be directly extend the keyword `delegate` with being 100% backwards compatible, splitting it in to a library prevents such a useful extension.

The problem is that "delegates" are not so core to D (unlike C# for example). D has delegates proper, function pointers, anything that implements opCall, etc. Under the hood they are completely different. The most common solution in current D is duck-typed templates and alias parameters; I think  very few people use delegates proper in D in practice. And the direction is not clear even in the spec:

https://dlang.org/spec/function.html#closures
«Future directions: Function pointers and delegates may merge into a common syntax and be interchangeable with each other.»

Another secondary point is why do you need this into an overloaded operator. Giving new meanings to old operators makes the code a little bit shorter but possibly less clear. I believe Walter picked ~ instead of + for string concatenation for this reason, so even in the std library the operator overloading is going to be a tough sell.
June 26, 2019
On Wednesday, 26 June 2019 at 12:51:29 UTC, XavierAP wrote:
> On Wednesday, 26 June 2019 at 12:16:19 UTC, Aphex wrote:
>> On Wednesday, 26 June 2019 at 09:48:35 UTC, angel wrote:
>>>
>>> Indeed it looks like a library solution.
>>> Its alleged usefulness has nothing to do with being a library as opposed to being a part of the core language. Normally, the core language implements stuff that is very hard or very ugly to implement in a library, and your proposal will look perfect in a library implementation.
>>
>> 1. The problem with a library solution is that it requires it part of the library. This is not alway effective. It would need to be part of phobos so at least it is used every. Not everything is a candidate for libraries even if they work in libraries. Virtually everything can, in theory, be a library solution. One could have make delegates a library solution, it is possible and functional... but it is too common and it loses functionality.
>>
>>
>> 2. It is a direct extension of something that is already a core semantic in the compiler and since it can be directly extend the keyword `delegate` with being 100% backwards compatible, splitting it in to a library prevents such a useful extension.
>
> The problem is that "delegates" are not so core to D (unlike C# for example). D has delegates proper, function pointers, anything that implements opCall, etc. Under the hood they are completely different. The most common solution in current D is duck-typed templates and alias parameters; I think  very few people use delegates proper in D in practice. And the direction is not clear even in the spec:
>

This is simply factually false. Delegates are extremely fundamental to do. They are used everywhere. Lambdas are delegates, member functions are essentially delegates, many templates take lambas. Lazy instantiation use delegates. Delegates are everywhere. Functions are delegates that do capture the context implicitly(and so can be optimized) and member functions have their context passed as an argument implicitly.


> https://dlang.org/spec/function.html#closures
> «Future directions: Function pointers and delegates may merge into a common syntax and be interchangeable with each other.»

> Another secondary point is why do you need this into an overloaded operator. Giving new meanings to old operators makes the code a little bit shorter but possibly less clear. I believe Walter picked ~ instead of + for string concatenation for this reason, so even in the std library the operator overloading is going to be a tough sell.

Because it was an example, something has to be used. I prefer + because it make sense and is used with -. ~ has no dual so it is less descriptive that it has a corresponding remove behavior.

~ used with strings makes sense because it is concatenation and one generally does not deconcatenate... but containers += and -= make more sense...

In any case the specific symbols are totally irrelevant. In fact, I've started using þµÆ etc. I write all my identifiers in extended ascii and make them unreadable so my code is hard to follow... but it is irrelevant because symbols do not have any inherent meaning.