Re: simplifying closures
- From: Havoc Pennington <hp redhat com>
- To: Karl Nelson <kenelson ece ucdavis edu>
- Cc: gtk-devel-list gnome org, kenelson sequoia ece ucdavis edu
- Subject: Re: simplifying closures
- Date: 04 Oct 2000 23:07:59 -0400
Karl Nelson <kenelson ece ucdavis edu> writes:
> [...]
>
> > But I'm saying there may only be one parent.
>
> I was stating what the design plan was, just for clarification. It
> wasn't really clear to others what the use of the lists where. (IMO)
>
No doubt, the original plan here has never been posted. We only know
it from talking to Tim in person when he was visiting here recently.
> That is a very big unless. I don't see how you can solve the problem
> of the object which created the closure used up the invalid and free,
> and the connected object now wishs to add something. It now
> must add an intermediate proxy which is twice the cost and then it
> needs to know how to chain. It does not seem like a very nice solution.
>
But to what problem, that's my question.
There are thousands of GTK+ apps; I've myself written quite a few
lines of GTK+ code; I've never thought of using features like this.
> Note, that my original closure proposal was for a (invalid, dtor, marshal)
> with a data node chain to store the chains to get over the limitation.
> But data node chains take more memory than what he implemented because
> each data node was one pointer, the malloc overhead and data itself.
>
Sure. I'm willing to let the uncommon case be more expensive.
>
> > 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.
>
> Where do you store the chains?
>
The chaining would be done by the closure user; "user" here would
pretty much mean language binding implementors, I would expect.
I notice throughout your mail you are taking GClosure to be a feature
C app programmers use frequently, no doubt based on the gtk--
experience. However no one has noticed a lack here in the C interface.
At least, I haven't heard anything about it reading all the user
lists. And your mail contained the first use case I've seen for that.
My understanding of GClosure has been that it's a simple abstraction
of connect_full(), in essence. We haven't put any discussion energy
into the end-programmer API.
> Is treating closures like function pointers a worthy goal? If so
> than what Tim is implementing is on target. Considering we are trying
> to evaluate the use of all future glib and gtk+ users and not simply the
> current use of gtk+. I can't say any of us have the proper foresight
> to know for sure which is the right path.
>
Thus the discussion. We need to try and pick the right path. Simply
designing the most flexible API does not result in the right path.
> I still do not know if GtkAction will solve every case of reusable
> closures. Further, with GtkAction you are going to have to hold
> the closure in the GtkAction. When you create an object from
> the Action you will need to connect. This is a clear case of
> multiple objects.
You simply connect a handler which invokes the action, the action is
the user data. Works quite nicely; see Bonobo.
> If closure are not copiable, you will need 2 closures to make the
> connection to one item. One in the menu to call the GAction, and
> the user supplied one in the GtkAction. Result all GtkActions double
> the number of closures. All GtkActions count as one of the multiple
> use cases.
>
So you get two closures; this is IMO the Right Thing. It keeps the
code functionally separated and gives you nice firewalling and
modularity, with no complexity to worry about. I would consider the
extra memory here worth it. (Though, Tim says the signal system
doesn't always store a closure internally, and in this case the
connection to the menu item "activate" would just be a function
pointer I believe.)
> And I ask what would the user have to do in your system if they
> need to copy a closure?
>
They recreate the closure from the original callback/data pair. There
isn't a need to copy because you can always just chain, as in the
GtkAction case.
I'm taking the closure as a callable object abstracted over
programming language. i.e. it could be a Scheme function, a sigc++
slot, a C callback. Given that, when writing original-language code
you always have the necessary information to recreate the closure.
You are taking closure as an end-programmer API for C. This is why we
disagree, in essence.
> In libsigc++, the argument was simply this.
>
> (1) A closure is an object.
> (2) A closure should be act like a function pointer.
> (3) Function pointers can be copied.
> (4) Closures can be derived.
>
> (R1) You can't copy a closure directly because that would be a
> deep copy of a derived object. ( 2 & 4)
> (R2) Therefore, it must be a ref counted object.
> (R3) Thus the process of binding it to an object must not be
> destructive, because function pointers can be used
> multiple times. (3)
> (R4) If it has only one invalid function then the connecting
> is destructive.
> => We need multiple parents.
>
> Are there any assumptions here you feel are invalid?
Yes. 1) I don't think means much; 2) I don't agree with, for the above
reason (it is a language binding internal, so who cares how it acts).
> I think that if closures don't act like function pointers they
> will feel quite restricting and thus be likely require difficult
> kludges for get around the limitation. Therefore, closures will
> be basically useless to C coders.
>
Right, I agree with that. I guess I'm arguing that they _should_ be
useless to C coders. ;-)
> Then you missed the point. (in psuedo code)
>
> struct MyData {
> GtkWidget *w1,*w2;
> };
>
> GClosure* make_closure(GtkWidget *w1, GtkWidget *w2)
> {
> MyData *d=my_data_new(w1,w2)'
> GClosure *c=g_closure_new(&function,d,&g_closure_free_data);
> g_closure_add_dependency(closure,w1);
> g_closure_add_dependency(closure,w2);
> }
>
> Here we have a fairly simple case in which the data is a structure which
> holds two objects, if either of them are destroyed we want to
> invalidate the closure.
>
OK, so this is the first concrete use case in C so far posted from you
or Tim. ;-)
I would say: if we just proposed this API out of the blue, with no
reference to language bindings, I wouldn't go for it. I don't see the
point.
> But you would be best to discuss this with Tim. I am not
> a very strong proponent of multiple data objects.
Right, ideally Tim will be participating here of course.
> You are completely avoiding specifying what you would do in the
> specific cases I mentioned. I really don't care whether you see
> them as common or not. If they are realistically possible and
> your system doesn't cover them, then I want to know how you intend
> to cover them.
I _don't_ intend to cover the cases, that's what it comes down to.
I'm proposing that GTK+ punt this issue, at least for now.
(Note that the simpler API is a subset of the complex one; especially
if we were to make closure an opaque object we could move to the
complex system in future versions. But, I don't expect anyone to go
for that.)
> The problem I see with chaining without copying or cloning is that
> you end up with a stew of proxies all of which you must handle
> dependencies between.
>
Only if multiple code sections add user data. If they don't, then
you simply have one proxy from one code section, chaining to user data
that section has control over.
> This is a reasonably large pool of disjoint reasons.
> Because they wanted to copy signals to make their code simpler.
> Because C++ STL requires that objects in lists be copiable. Because
> they needed to transfer ownership of a closure from one place to
> another in an event queue. Because it would allow simpler binding
> of keys, menus, and buttons in a XML builder for gtk--.
>
OK. None of that applies to C that I can see.
> These are very fuzzy arguments. Maintainence time, implementation
> complexity, etc are not quantitative.
And? They are still pretty important.
> This system is very similar to the libsigc++ in this regard.
> I think the idea is that this closure system is to be a largely
> closed system. It is extendable by deriving but for all C users
> it should be a black box.
>
I don't see black box here. I see a different interface. We can have:
g_closure_new (callback, data, dnotify, inotify, preinvoke, postinvoke);
g_closure_set_dnotify();
g_closure_set_inotify();
etc.
or:
g_closure_new (callback);
g_closure_add_dnotify ();
g_closure_remove_dnotify();
g_closure_add_inotify ();
g_closure_remove_inotify();
etc.
which is a pretty different interface. And as I've said, I haven't
seen any discussion of interface, or C use-cases. I've only seen "well
we need to handle this that and the other cases."
I think the right approach is to outline the API, then outline the
implementation. What surprised Owen and I, I'm guessing, is that we
expected simply an abstraction of connect_full() so that language
bindings would be able to uniformly handle foreach(), signal
connections, main loop, etc. But the eventual API is apparently
targetted to a general-purpose C-language programming feature, which I
at least don't see the motivation for. What is the problem or user
request it addresses? What's the benefit of the complexity?
> assertion is flawed. You are saying no one has yet requested
> closures be copiable when closures don't exist.
>
Let me clarify: I'm saying no one has requested an abstraction of C
function/user_data pairs. I haven't seen the motivation for _using
closures in C applications at all_.
Havoc
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]