std.signals2 proposal

Robert jfanatiker at gmx.at
Mon Nov 5 05:36:55 PST 2012


Hi there!

I just developed a proof-of-concept implementation of an improved
std.signals. 

Things I did not like about the current implementation:

1. Passing a delegate to connect, which basically is an extended (void*)
and assuming it is an object delegate, does not feel quite right in D,
where we usually try to guarantee correctness by the compiler wherever
possible.

2. The restriction that only objects can be connected. 

Point 2 does not really bother me, because from my little experience I
never really had connected anything else than objects to a signal. But
the restriction that the connected delegate must not be some wrapper, is
quite a restriction I came to dislike about Qt and even more so with
this implementation because unlike Qt the signatures have to match
exactly, you can't omit parameters, like: 
// Qt code
connect(button, SIGNAL(clicked(bool)), this, SLOT(buttonClicked());

-> In the current implementation buttonClicked would have to take a
bool.

In addition boost::signals together with boost::bind offered even more
comfort like passing additional parameters to a slot, which is really
very, very useful:

for(int i=0; i<buttons.length(); i++) {
	buttons[i].clicked.connect(boost::bind(&MyObj::addNumber, this,
 i)); 
}

So I tried to improve std.signals, code:
(warning at least one bug is remaining, explained below)

https://github.com/eskimor/phobos/tree/new_signal

You can easily connect to an object's method with: 

obj.signal.connect!"someMethod"(obj);

instead of the old implementation:

obj.signal.connect(&obj.someMethod);

-> The interface is clean and type safe, all the ugly details are hidden
from the user. And it is just one character more typing. Simple things
stay simple.

In addition a method allowing wrapper delegates was added:

class Observer {
	void addNumber(int i) {
		sum+=i;
	}
	int sum;
}

class Button { 
	Signal!(bool) clicked;
	// ...
}

void main() {
	auto b=new Button;
	auto o=new Observer;
	// Ignore boolean parameter and pass some int:
	b.connect!Observer(o, (o, p) { o.addNumber(7); });
	// or even:
	b.connect!Observer(o, (o, p) => o.addNumber(7));
	// For some reason the compiler is not able to deduce "o" being
	// Observer, so the !Observer is needed, but it is still very
	// neat and readable.
}

Thanks to D's lamdas the syntax is even more concise as boost::bind and
far more powerful.

By passing the object explicitly to the delegate, it is possible to
maintain the 'weak ref' semantics to the target object, while ensuring
that the delegates context won't be freed.

As a side effect it is now even possible to use struct delegates or even
any non object delegate. Simply pass null for the obj parameter. It is
completely safe, the only drawback is that the struct won't be deleted
until the Button gets destroyed. (Because it holds a reference to the
struct, by means of the delegate.) But for long lived structs this
usually is perfectly acceptable.

Implementation:

In my implementation I changed the Signal mixin to be a simple template
struct, because I hit strange compiler errors with it being a mixin. The
most prominent:

std/signals.d(443): Error: no overload matches for connect(string
method,T2) if (is(T2 : Object))

You can find the version triggering these errors at:

https://github.com/eskimor/phobos/tree/new_signal_mixin

Also I did not really get the point why a mixin was used in the first
place, it does not really gain us anything? What was the reasoning about
it?
I almost thought I found the reason, because my implementations suffers
from unhook not being called, although it was properly registered with
"rt_attachDisposeEvent(obj, &unhook);", thus causing a segmentation
fault when the observer gets deleted. I did not really find any
difference from the original version that could explain this behavior,
despite the original implementation being a mixin. So I thought, well
maybe the delegate passed to "rt_attachDisposeEvent(obj, &unhook);" must
be an object delegate (that's would be why the mixin was needed), but
after digging in object_.d I did not find any code assuming that the
delegate was an object delegate. Any ideas on this?

Another thing I'd like to ask Walter, is what the "L1:" label is for in
connect(), is it just some left over or has it some special internal
compiler thing meaning?

What do you think?

Best regards,

Robert




More information about the Digitalmars-d mailing list