Suggestion: signal/slot mechanism

Kristian kjkilpi at gmail.com
Thu Sep 7 00:07:38 PDT 2006


On Wed, 06 Sep 2006 03:50:02 +0300, Lutger <lutger.blijdestijn at gmail.com>  
wrote:
> Kristian wrote:
> <snip>
>> Bruno Medeiros wrote:
>>
>>> I see, so a S/S object also knows which signals point to his slots.  
>>> Still, a base class is not required, a mixin can do the job nearly as  
>>> well.
>>>
>>>> I think everybody will agree if I say that the optimal solution would  
>>>> be D supporting S/S mechanism directly.
>>>>
>>>
>>> I disagree. If D can support S/S without additional languages  
>>> constructs, and with nearly the same easy of use as Qt's, then that's  
>>> enough.
>>   Well, I think the key sentence here is "almost as easy as". Will it  
>> be easy enough for the majority?
>>  The S/S mechanism is a very simple structure. If you build it 'right',  
>> then there is no room for competition (because you cannot make it  
>> simplier). And being simple, it won't bloat the language. Instead it'll  
>> extend the language 'naturally'.
>>  Mixins and templates would make the mechanism quite complex. That  
>> would bloat the *code*.
>
> Of course you are right that built-in S/S can be cleaner (and more  
> efficient), IIRC C#'s events are something like it. But it also has some  
> disadvantages, for example boost::signals is quite different than QT's,  
> and the latter also has features for introspection. These libraries (as  
> a whole) are higher-level than is reasonable for D to incorporate into  
> the language.
>
> I'm also not saying that dcouple is poorly written, but I think it can  
> be done a little clearer. This thread inspired me to work on the managed  
> part of my sigslot module, below I've added how your example looks like  
> in it. It also handles other callable types (like free functions). There  
> is no need to derive from any class or interface.
>
> class Foo {
>      void setValue(int value) {
>          ...
>          valueChanged1();
>          valueChanged2(oldVal, value);
>      }
>
>      Signal!() valueChanged1;
>      Signal!(void, int, int) valueChanged2;
> }
>
> class Bar {
>      this() {
>          foo = new Foo;
>          foo.valueChanged1.connect(handleFoo(), this);
>
>          foo2 = new Foo;
>          foo2.valueChanged2.connect(handleFoo(), this);
>      }
>
>      void handleFoo() {...}
>      void handleFoo(int oldValue, int newValue) {...}
>
>      Foo foo, foo2;
>
>      mixin SlotObjectDestructor;
> }

Well now, this is almost like having a direct support! :)

One have to use a mixin and the signal names cannot be overloaded.  
However, these are not big drawbacks at all. Actually the overload thing  
is a benefit when connecting: you don't have to define parameter types. I  
like the syntax very much indeed; this is a must for Phobos...! ;)

At first I thought that I would like the 'connect()' function to have  
parameters swapped (e.g. "foo.valueChanged1.connect(this, handleFoo());"),  
but now I think the current order is better.

BTW, I'm wondering why there is 'void' in "Signal!(void, int, int)"? Is it  
intentional?



>
>> For example:
>>  /* ----- Qt-styled way with some modifications */
>>  class Foo {
>>     void setValue(int value) {
>>         ...
>>         emit valueChanged();
>>         emit valueChanged(oldVal, value);
>>     }
>>      signal valueChanged();
>>     signal valueChanged(int oldValue, int newValue);
>> }
>>  class Bar {
>>     this() {
>>         foo = new Foo;
>>         connect foo.ValueChanged() to handleFoo();
>>          foo2 = new Foo;
>>         connect foo2.valueChanged(int, int) to handleFoo(int, int);
>>     }
>>      void handleFoo() {...}
>>     void handleFoo(int oldValue, int newValue) {...}
>>      Foo foo, foo2;
>> }
>>   /* ----- dcouple-styled way */
>>  // The Widget class implements the S/S handling.
>> // It's derived from dcouple's SignalSlotManager.
>> // Its implementation is omited here.
>>  class Foo : Widget {
>>     this() {
>>         sigValueChanged1 = new Signal!()(this);
>>         sigValueChanged2 = new Signal!(int, int)(this);
>>     }
>>      void setValue(int value) {
>>         ...
>>         sigValueChanged1.emit();
>>         sigValueChanged2.emit(oldVal, value);
>>     }
>>      Signal!() sigValueChanged1;
>>     Signal!(int, int) sigValueChanged2;
>> }
>>  class Bar : Widget {
>>     this() {
>>         foo = new Foo;
>>         slotHandleFoo1 = new Slot!()(this, &handleFoo1);
>>         connect(foo.sigValueChanged1, slotHandleFoo1);
>>          foo2 = new Foo;
>>         slotHandleFoo2 = new Slot!(int, int)(this, &handleFoo2);
>>         connect(foo2.sigValueChanged2, slotHandleFoo2);
>>     }
>>      void handleFoo1() {...}
>>     void handleFoo2(int oldValue, int newValue) {...}
>>      Slot!() slotHandleFoo1;
>>     Slot!(int, int) slotHandleFoo2;
>>      Foo foo, foo2;
>> }
>>  I am not saying that dcouple is poorly written or anything (far from  
>> it!), but the benefits of the direct support is obvious IMHO:
>>  - Less code (and less potential bugs).
>> - Clearer syntax which is easier to read/write.
>> - Signals can be connected to any object and function.
>> - No need to use a base class (or mixins).
>> - Function overloads can be used.
>> - Guaranteed to be bug free.



More information about the Digitalmars-d mailing list