Re: simplifying closures



Karl Nelson <kenelson ece ucdavis edu> writes:  
> The things on the "invalid" list are 
>   - removing the dependencies if multiple objects can invalidate the
>     closure. (example two objects are in a structure and when 
>     either of them are removed the closure should not be called.)

For this case I'm proposing that the two objects would notify the
structure, and the structure would notify the closure. One data chunk
on the closure. The objects are invalidating the closure though;
there's no notifier stored on the closure here.

>   - the parent (or parents) of the closure which may need immediate
>     action to save resources.  (example: a timeout routine needs to 
>     clean up in the event that the closure becomes invalid because 
>     when the timeout gets called the closure won't be there.)
> 

But I'm saying there may only be one parent.

> These functions are supplied at different times (one by the creator of
> the closure and one by user or the closure) and thus unless you want to 
> make them chain, they need to be stored separately.
> 

You say "these functions"; what is the first one. I don't see what the
function is for your first bullet point.

To clarify, I'm suggesting four function pointers in the closure, but
only one of each type (dnotify, inotify, pre, post).

> (chaining is a major pain.  You can see chaining used in a number
> of places in gtk+ currently where the user asked for a connection
> and we instead create another connection to proxy over to the 
> first function.) 
>

It isn't a major pain in any cases I see. It's just a bit of trivial
typing, and it's simple to understand. Concrete examples in existing
GTK+ code?

Do you agree however, that chaining _can be used_ to always solve the
problem, unless we have a case where user and library code are each 
adding separate notifiers. 

However, if you make the changes I've suggested (no closure reuse, one
piece of user data) I can't think of such a case. Can you give a
concrete example of a case where the library would supply a notifier,
and also the user, given my changes (single parent, single user data),
which could not be handled by chaining.
 
> The point in the implementation is "caveman counting".  A caveman 
> sees no objects, one object, many objects.  That is that if you 
> are going to require more than one routine you might as well pay
> the cost for as many as needed.  Considering the cost of 
> two and many is approximately the same this makes sense.
>

I agree, but I'm saying we choose 1 object. One notifier of each type.
The choices are 0, 1, or N, and I say 1, not N.

> > 1) A closure can only be connected to one signal, main loop source,
> >    or whatever. i.e. only one "connection" per closure.
> >    This means you only need one invalidity notifier to "disconnect" 
> >    the closure.
> 
> This is bad because there are multiple partners which need to be
> informed in the case of multiple pointed to objects or multiple
> parents.
>

I said "we define closure to be connectable to only one parent and
user data" and you say "this is bad because you can have more than one
parent or user data."  Not convincing!

> >    One is saving space; by reusing closures, you can have
> >    fewer. However I think it's clear that closures can only be reused
> >    about 5% of the time, and if we can avoid multiple notifiers, we
> >    can save more space the other 95% of the time.
> 
> What numbers to you have to support this?  
> 

I see very few signal handlers reused in GTK+. It's very rare that
someone connects the same function to more than one place.
 
> This was not the only reason for reuse of closures.  A classic example
> of reuse of closures is systems like MDI where an item is used over and
> over repeatedly be the system.  Everytime the info is used to build a
> menu the closure gets another connection.  The user may not know that 
> the closure is being used more than once.  I believe having this
> ability is likely going to be important.
> 

You just create an abstraction such as the GtkAction I've proposed in
the past, or the verb abstraction in Bonobo embedding. The abstraction
is better than using closure anyway, because it's appropriate for the
domain.

Closures should be an abstraction of gtk_signal_connect_full(), not an
attempt to solve all problems ever.

But even if I say "OK, you could use it in MDI," that's an instance of
where it would be mildly convenient to do so; it's certainly not a
necessity. When is it _needed_?
   
> >    However this can be alternatively solved as follows: when the 
> >    alive object is destroyed, for each closure stored on the 
> >    object, first remove the destroy notifier from the closure,
> >    then invalidate the closure. Connect a destroy notifier 
> >    from the closure to the alive object which removes the closure
> >    from the list on the object. (The basic summary of this 
> >    approach is: use the destroy notifier to strip the closure 
> >    from the alive object.)
> 
> I am not sure which you mean be the destroy notifier here.
> The destroy notifier is often a call to g_free to free
> the structure pointed to.  
>

The destroy notifier is simply the function in the destroy notifier
slot in the GClosure.

In the case where you have an alive object, this would not
unref/destroy the object, instead it would strip the closure out of
the list of closures the alive object is an alive object for.

If you want to allow an alive object _and_ user data, you'd have to
use chaining.
 
> How does this handle the case when 2 object have a peice of
> data which points to the closure to tell the closure to 
> invalidate
> 
>     A
>      \ 
>       C
>      /
>     B
> 
> How does the A going away remove B's invalid call?
>

You can only have:

 C - A - B

or:
           A
          /
 C - Proxy 
          \ 
           B

Because there's only one user data.
 
> Chaining is error prone.  It means that each time you connect you
> must store the old routine.  Where do you plan to store it?  
> In other words you just pushed to problem to the user to store
> where before we stored it in our lists.  In other words, you
> are going to have to add some sort of data list to store the chains.

Well, I'm not going to have to do this because I don't think GTK+
needs to support multiple user data. libsigc++ and other language
bindings could do it, but that's still in a library, not in user code.
You might say we should share this between bindings; but I don't think
we have evidence that other bindings than libsigc++ even want to use
it, or that the abstraction we're considering will work for them. 

> (Note, this is what libsigc++ does.  It uses chaining and has to
> provide a chaining list to store its data.)

Right. An important point is that this simplification in GTK+ does
_not_ affect the public API of libsigc++ or gtk--; you can still
present whatever interface you like.
 
> There are cases in the gtk+ code where the closure was created
> with some destruction routine then the caller who receives the
> closure adds another.   If we have to proxy and chain here the
> the cost of the system multiples by each object in the chain.
>

Can you point to the concrete case you are talking about?
 
> I would argue that being about to take a closure and reuse it is
> enough of a justification for multiple parents.  This is a feature
> which libsigc++ lacked and has become a sore point because it actually
> did occur.  

Concrete examples? Why did they want to reuse, what did it get them,
and how bad was the alternative way of coding it? 

I have to say I don't find this too much different:

  Slot s = slot (obj, &method);
  connect (s);
  connect (s);

vs. 

  connect (slot (obj, &method));
  connect (slot (obj, &method));

it's just not that bad a problem.
   
> I think you are also missing a major point of a library.  The
> point of a library is to solve hard problems so that the user
> doesn't have to.  Thus no matter how complex the inter workings
> of the implementation, so long as the interface is simple
> it is good.

There are a number of problems with complexity in a library.

 - it locks you into a larger interface which is harder to
   keep compatible, both because it's larger, and because
   it has subtle interactions with the rest of the API
 - it takes maintainer time away from implementing other stuff
 - it's hard to maintain, and typically buggier
 - it is more bloated
 - 95% of the time, implementation complexity leaks into the 
   interface

There are many tradeoffs which add up to "complexity bad." Here we are
talking a major increase in complexity, with no demand _at all_ from
people using the GTK+ C interfaces to write apps. I haven't seen a
single post asking for any of these features. However there are many
frequently-requested features we haven't gotten around to.

For language bindings, I think we should provide what they _need_,
it's OK if there's a bit of cruft involved in the language binding
code.

Anyway, rather than going through all this theoretical goo: what are
some concrete use-cases where you _need_ multiple user data or
multiple parents? Ideally some in C applications.

Havoc




[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]