www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Multiple delegates, contextual delegates

reply Aphex <Aphex mail.com> writes:
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?
Jun 24 2019
parent reply angel <andrey.gelman gmail.com> writes:
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 ?
Jun 26 2019
next sibling parent reply Aphex <Aphex mail.com> writes:
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.
Jun 26 2019
parent reply XavierAP <n3minis-git yahoo.es> writes:
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.
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.
Jun 26 2019
parent Aphex <Aphex mail.com> writes:
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.
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.
Jun 26 2019
prev sibling parent XavierAP <n3minis-git yahoo.es> writes:
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 ?
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
Jun 26 2019