std.signals2 proposal

Robert Klotzner jfanatiker at gmx.at
Tue Nov 6 04:10:24 PST 2012


First a more direct link to the code:
https://github.com/eskimor/phobos/blob/new_signal/std/signals.d

> Not sure I understand why such hatred is rational?
I don't know where you saw hate. I just believe that a strong type
system is a good idea to avoid many errors, that's one of the reason I
am a C++ and D developer and not use some clumsy scripting language
where everything blows at run time. I believe that you should have a
type safe public API where ever possible and if you need a delegate
pointing to an object, you should state that, if it is possible. The
only reason Walter probably has chosen this way, is that he most likely
had something in mind to lift this restriction in the future. Which
would be, in my opinion the only valid reason to leave it the way it is.
In the current situation it would not be possible to use signals from
safe D, in my version you can mark it as trusted.

I wrote my version to show that very powerful signals are possible
within the current language and without proper weak references or
something. I also don't know how Walter would have intended to support
wrapper delegates for parameter matching in the current design. I can't
think of any language feature that would make this possible. (You would
only have an opaque pointer to a delegates context, which somehow has a
reference to the target object. You need a weak ref to the target
object, but a strong ref to the delegates context. If the target ref is
contained in an unknown way in the delegates context, there is no way to
achieve this.)

> in emit:
> 
> if(slot.indirect.ptr) {
>   <stuff>
> }else{
>   <otherstuff>
>   slot.indirect.ptr = <thing>
>   slot.indirect(i);
> }
> 
> <stuff> will not be executed more than once?
<stuff> is: 	"slot.indirect(cast(void*)(objs[index]), i);"
and gets executed for every slot. I am not sure I understand your
question.

> 
> in addSlot:
> 
> you use malloc &friends to allocate objs, but the gc to allocate slots? 
> The correct thing to do here is use allocators, which we will totally 
> have fleshed out by Christmas (right, Andrei?).
> 

Yes, because I need the references in slots to be considered by the gc,
I could equally well have used malloc and afterwards add it to gc roots,
but the current implementation is just proof of concept, not a perfectly
tweaked one. If people want it in phobos I would most certainly try to
make it as good as possible first.
> in connect [Object]:
> 
> <Tony Shalhoub voice> It's not formatted right! </Tony Shalhoub voice>
> 
> You might s/is(T2 : Object)/is(T2 == class)/

well yes, because every class derives from Object. But the requirement I
need is that T2 is derived from Object, so it makes the intent more
clear.
> 
> Also, it looks like it will not delegate to any overriding methods, but 
> I don't know if that is desired. I doubt it differs from existing 
> std.signal.
> 
> invariant:
> 
> > 	assert(slots_idx==slots.length); // I probably even remove slots_idx all together.
> 
> Yes you should. Also, you should assert that slots.length == objs.length.
-> No I should not, at least in the current implementation this would be
wrong. I did not yet made up my mind how I want to implement it in the
end, so I left it for now.
> 
> private:
> > union DelegateTypes
> > {
> > void delegate(void*, T1) indirect;
> > void delegate(T1) direct;
> > }
> 
> Could you explain why indirect must have its first param void* ?
Well it does not have to, but I know for sure that a cast to void* is
just a reinterpretation. I could equally well have used Object, because
at least in my understanding of a non-multiple inheritance hierarchy,
the cast to Object should not change the pointer value either. But in
fact it does not really matter as you don't do anything with it, other
than passing it on. It would avoid some casts though, so I will consider
it.
> >
> > 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 improvement is where?
That you can not pass some arbitrary slot like:

obj.signal.connect(() { /* my very funny delegate. */ })
which would compile with the standard implementation but would fail
badly at runtime.

> > 	b.connect!Observer(o, (o, p) => o.addNumber(7));

> Nice.
> Should that be b.clicked.connect?
Yes, it should be. Thanks.
> 
> >
> > 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.
> 
> When would the delegate's context be freed? I think it wouldn't.
If you keep the delegate references in non gc managed memory, the gc
will not see any references to the delegates' context if client code
does not keep a reference, which is very likely for lamdas and thus will
free it in the next collection cycle. If the delegates context is not
the object itself, you need a strong reference to it.
> 
> >
> > 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.
> 
> I like this capability, but the api could handle the case of non-object 
> delegates better.
> 
> <nitpick> Not completely safe if the struct was allocated on the stack 
> </nitpick>
I knew this was coming. Well I haven't done anything with closures yet,
the usage is an escaping of a reference so the compiler should allocate
it on the heap, if not than you got a problem, but its not specific to
signals -> So I still consider the signal API safe ;-)

> 
> > 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?
> 
> So you can do b.connect in the simplest case?
Yes, that's the only reason that comes to mind. I personally think that
a struct is a cleaner solution, that also avoids cluttering the
containing objects namespace.
> 
> >
> > What do you think?
> 
> I like the way this is headed.

Thanks, and thanks for you review.




More information about the Digitalmars-d mailing list