September 04, 2006
Kristian wrote:
> On Sun, 03 Sep 2006 13:01:28 +0300, Bruno Medeiros <brunodomedeiros+spam@com.gmail> wrote:
>> Kristian wrote:
>>> It would be nice if D had a signal/slot mechanism similiar to Qt:
>>> http://doc.trolltech.com/4.1/signalsandslots.html
>>>  It's an elegant way to handle messages sent between objects. It beats event table declarations used by other GUI libraries IMHO.
>>>  It would make D a lot more appealing language to write GUI applications. Think of wxWidgets written in D... ;)
>>>   I think it would be quite simple to build a S/S support for a compiler (at first glance, at least). For example:
>>>  The 'Object' class has a pointer to S/S data (it's null if the object don't currently use signals/slots). S/S data holds a slot list for each signal. It also holds a list of objects that have slot(s) connected to this object's signal(s). This list is used to disconnect necessary slots at a destruction of the object.
>>>  When the compiler reads a 'emit X' statement, it will do two things. First, it generates an id for the signal which is used to retrieve a correct slot list.
>>>  Second, the compiler puts the signal's parameters to the stack as it would call a corresponding function. Instead, the 'Object._emit_signal(id)' function (or something) is called, where 'id' is the generated id. (Note that there are no function bodies for signals.) '_emit_signal()' retrieves the correct slot list, and calls all the slots (delegates) in it. Finally the parameters are removed from the stack.
>>>  Of course, slots should not modify their parameters so that all the slots will receive the same parameter values. Hence slots should not use the 'out type'. There is a market for a 'const type' here... *wink*
>>>   Maybe there should be no slot keyword at all as there is in Qt. You don't need to declare a function to be a slot; all the (virtual) functions can be used with signals.
>>>  Because the return values of all the signals are void, the void typeword could be removed from signal declarations.
>>>  signal clicked();
>>>  signals:
>>> clicked();
>>> clicked(int button);
>>>   BTW, Qt generates ids for signals as follows:
>>>  signals:
>>> void clicked(int button, bool isMoved);
>>> -> the id is a string "clicked(int,bool)"
>>
>> The Signal and slots pattern is little more than an abstraction for languages that do not support delegates (and dynamic arrays). Which is not the case for D:
>>
>>    // Declare a signal:
>>    void delegate(Button, int)[] someSignal;
>>
>>    // Connect a slot to a signal:
>>    someSignal ~= foo.someSlot;
>>
>>    // emit the signal
>>    foreach(dg; someSignal)
>>      dg(myButton, myInt);
>>
>> The only limitation I see with D so far, is on the emit part. You can't create an emit function that works like this:
>>    emit(someSignal, myButton, myInt);
>> and that would do the same as that foreach. Because you cannot do "parameterized"(whether compile time or runtime) function calls.
>>
> 
> Automatic S/S disconnection at object destructions should also be implemented, usually, which requires the use of a base class.
> 

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.

> It would make using of signals/slots as easy as calling of functions, for instance. But if Walter (and D community) thinks that the S/S 

With a library, in D, S/S can be used as easy as calling functions.


-- 
Bruno Medeiros - MSc in CS/E student
http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
September 04, 2006
On Sun, 03 Sep 2006 20:08:36 +0300, Kristian <kjkilpi@gmail.com> wrote:

> On Sun, 03 Sep 2006 13:01:28 +0300, Bruno Medeiros <brunodomedeiros+spam@com.gmail> wrote:
>> Kristian wrote:
>>> It would be nice if D had a signal/slot mechanism similiar to Qt:
>>> http://doc.trolltech.com/4.1/signalsandslots.html
>>>  It's an elegant way to handle messages sent between objects. It beats
[snip]


Ludger wrote:

> I think there are benefits / downsides to both options of language and library implementation. Personally I favor a library option, and if it would be included in some (de facto or official) standard library that would be good indeed.
>
>  In C++ the existing library solutions are very nice and prove that a preprocessor like QT uses is not needed at all. I think it can be done in D even better, because of delegate support and template features.
>
>  The benefit of a library solution is less language bloat, more flexible   and possible competition. Some libraries also perhaps don't want to use the signal-slot mechanism, like harmonia which prefers to use sinking-bubbling.


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*.

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.
September 06, 2006
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;
}

> 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.
September 07, 2006
On Wed, 06 Sep 2006 03:50:02 +0300, Lutger <lutger.blijdestijn@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.
September 07, 2006
The approach (simplified), I currently use:

> class Foo
> {
> 	enum EV {VAL_CHANGED,}
> 	void emit(EV ev) {
> 		auto fn = ev in subscribers;
> 		try { if (fn != null) (*fn)(); }
> 		catch (Exception wtf) { writefln("WTF? %s", wtf); }
> 	}
> 	void sub(EV ev, void delegate() fn) {
> 		subscribers[ev] = fn;
> 	}
> 	int val(int i) {
> 		value = i;
> 		emit(EV.VAL_CHANGED);
> 		return value;
> 	}
> 	int val() {
> 		return value;
> 	}
> 	void delegate()[EV] subscribers;
> 	int value;
> }
> 
> class Bar
> {
> 	this(Foo f) {
> 		foo = f;
> 		foo.sub(Foo.EV.VAL_CHANGED, &receive);
> 	}
> 	void receive() {
> 		writefln("received %d", foo.val);
> 
> 	}
> 	Foo foo;
> }
> 
> void main()
> {
> 	auto f = new Foo();
> 	auto b = new Bar(f);
> 	f.val = 1;
> 	delete b;
> 	f.val = 2;
> }

-- 
serg.
September 07, 2006
Kristian wrote:
<snip>
> 
> 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...! ;)

Well thanks. I'll have to make some more tests before putting it online somewhere, then I'll see what it's worth.

> 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.

It's more consistent with free functions / unmanaged slots:

sig.connect(&fooFunc);

// alternative syntax:
sig ~= { writefln ("hello world"); };

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

Yes, it's the return value of the signature. Normally a signal returns whatever the last slot returns, if it does. With foreach traversal, you can do something similar to combiners in boost::signals. See http://www.boost.org/doc/html/signals.html.

Here's an example:

// sum for Signal!(int, <any type>)
int sum(SIGT, ARGT) (SIGT signal, ARGT arg)
{
    int result;
    foreach(slot; signal)
        result += slot(arg);
    return result;
}
	




1 2
Next ›   Last »