Multiple delegates, contextual delegates

Aphex Aphex at mail.com
Wed Jun 26 12:16:19 UTC 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.









More information about the Digitalmars-d mailing list