TIP #379 Version 1.8: Add a Command for Delivering Events Without Tk

This is not necessarily the current version of this TIP.


TIP:379
Title:Add a Command for Delivering Events Without Tk
Version:$Revision: 1.8 $
Author:Will Duquette <will at wjduquette dot com>
State:Draft
Type:Project
Tcl-Version:8.7
Vote:Pending
Created:Sunday, 17 October 2010
Keywords:event

Abstract

This proposal defines the hook ensemble command, which implements the Subject/Observer pattern. It allows subjects, which may modules, objects, widgets, etc., to synchronously send events to an arbitrary number of subscribers, called observers. A subject may send any number of distinct events, and any number of observers can bind callbacks to a particular event sent by a particular subject. Event bindings can be queried and deleted.

Rationale

Tcl modules usually send notifications to other modules in two ways: via Tk events, and via callback options like the text widget's -yscrollcommand option. Tk events are available only in Tk, and callback options require tight coupling between the modules sending and receiving the notification.

Loose coupling between sender and receiver is often desirable, however. In Model/View/Controller terms, a View can send a command (stemming from user input) to the Controller, which updates the Model. The Model can then send an event to which all relevant Views subscribe. The Model is decoupled from the Views, and indeed need not know whether any Views actually exist.

At present, Tcl/Tk has no standard mechanism for implementing loose coupling of this kind. This proposal defines a new command, hook, which implements just such a mechanism.

Hook Events

The hook command manages a collection of event bindings. An event binding has four elements:

Subjects and Observers

For convenience, this TIP, collectively refers to subjects and observers as objects, while placing no requirements on how these objects are actually implemented. An object can be a a TclOO or Snit or XOTcl object, a Tcl command, a namespace, a module, a pseudo-object managed by some other object (as tags are managed by the Tk text widget) or simply a well-known name.

Subject and observer names are arbitrary strings; however, as hook might be used at the core and package level, it's necessary to have conventions that avoid name collisions between packages written by different people.

Therefore, any subject or observer name used in core or package level code should look like a Tcl command name, and should be defined in a namespace owned by the package. Consider, for example, an ensemble command ::foo that creates a set of pseudo-objects and uses hook to send notifications. The pseudo-objects have names that are not commands and exist in their own namespace, rather like file handles do. To avoid name collisions with subjects defined by other packages, users of hook, these ::foo handles SHOULD have names like ::foo::1, ::foo::2, and so on.

Because object names are arbitrary strings, application code can use whatever additional conventions are dictated by the needs of the application.

Specification

The hook command is an ensemble command with the following subcommands:

Bind Subcommand

This subcommand is used to create, update, delete, and query event bindings.

hook bind ?subject? ?event? ?observer? ?cmdprefix?

Called with no arguments, hook bind returns a list of the subjects with active bindings.

Called with one argument, a subject, hook bind returns a list of the events for which the subject has active bindings.

Called with two arguments, a subject and an event, hook bind returns a list of the observers which are bound to this subject and event.

Called with three arguments, a subject, an event, and an observer, hook bind returns the binding proper, the command prefix to be called when the event is sent, or the empty string if there is no such binding.

Called with four arguments, hook bind creates, updates, or deletes a binding. If binding is the empty string, hook bind deletes any existing binding for the subject, event, and observer. Otherwise, binding must be a command prefix taking as many arguments as are documented for the subject and event.

If the observer is the empty string, "", hook will create a new binding using an automatically generated observer name of the form ::hook::ob<number>. The automatically generated name will be returned, and can be used to query, update, and delete the binding as usual.

The subject and event can contain wildcards; the observer will be bound to all matching subjects and events. The wildcards will be resolved when events are sent.

The cmdprefix may contain %s, %e, and %o conversions, which are expanded at call time to the actual subject, object, and observer names.

Discussion

Subject/Event Order: It has been suggested that the event name should precede the subject name, as an observer might want to subscribe to the same event from all subjects. However, the subject has priority, as it is the subject that determines the events that are available, and the semantics of those events. A "cannon" object and a "house" object might both have <Fire> events, but it's unlikely that a single callback would reasonably handle both of them.

Optional Observers: It has been suggested that the observer argument should follow the cmdprefix argument; if it is omitted, an observer name would be automatically generated. However, the observer name is frequently used in practice, and is likely to be much shorter than the cmdprefix, which might be quite long. As a general rule, short arguments following long ones tend to get lost visually; keeping the observer before the cmdprefix leads to more easily readable code.

Send Subcommand

hook send subject event ?args...?

This command is called when the named subject wishes to send the named event. All relevant bindings are called with the specified arguments in the global namespace. Note that the bindings are called synchronously, before hook send returns; this allows the args to include references to entities that will be cleaned up as soon as the event has been sent.

The order in which the bindings are called is undefined. If sequence among observers must be preserved, define one observer and have it call the others in sequence.

Because the hook mechanism is intended to support loose coupling, it is presumed that the subject has no knowledge of the observers, nor any expectation regarding return values. (But see the discussion of the hook call command, below.) This has a number of implications:

If the -errordelete configuration option is true, bindings throwing errors will be deleted so that it doesn't happen again.

If the -bgerror configuration option is false, errors in bindings will propagate to the caller of hook send in the normal way, possibly preventing subsequent bindings from being called.

See hook configure, below, for more information on configuration options.

Forget Subcommand

hook forget object

This command deletes any existing bindings in which the named object appears as either the subject or the observer.

Configuration Subcommands

hook cget option

This command returns the value of one of the hook command's configuration options.

hook configure option value ...

This command sets the value of one or more of the hook command's configuration options:

-bgerror

This boolean flag, if true (the default), means that errors in binding scripts are handled by the interpreter's interp bgerror handler. If false, errors in binding scripts are propagated to the caller of hook send.

-errordelete

The boolean flag, if true (the default), means that bindings that throw an error are deleted, so that the error doesn't recur. I can see that this is a useful feature; in at least one of the applications in which I've used this mechanism, a setting of false is genuinely what's wanted. Hence, I've added this option.

-tracecommand

The option's value should be a command prefix taking four arguments: a subject, an event, a list of event argument values, and a list of objects receiving the event. The command will be called for each event that is sent. This allows the application to trace event execution for debugging purposes.

Example

The ::model module sends the <Update> event in response to commands that change the model's data:

   hook send ::model <Update>

The .view megawidget displays the model state, and needs to know about model updates. Consequently, it subscribes to the ::model's <Update> event.

   hook bind ::model <Update> .view [list .view ModelUpdate]

When the ::model sends the event, the .view's ModelUpdate subcommand will be called.

Later the .view megawidget is destroyed. In its destructor, it tells the hook that it no longer exists:

   hook forget .view

All bindings involving .view are deleted.

Possible Additions

During discussions on the tcl-core mailing list, members suggested a number of possible additions to the functionality described in the first draft of this TIP. Some small capabilities have been added in this draft; however, there are two significant ones that I have elected to defer, for two reasons:

Consequently, I'd rather not delay this TIP until these suggested additions are mature. If this TIP is accepted, then these additions can be considered as TIPs in their own right.

On the other hand, they are genuinely interesting, so I want to mention them here.

Asynchronous Dispatch

The hook send command calls bindings synchronously, returning after all bindings have been called. An asychronous mode has been proposed, where bindings would be called in the context of the event loop for even looser coupling between the subject and the observers. This is certainly doable; however, the same effect can be achieved by calling hook send in an after handler.

I've often considered adding a mode like this to our existing implementation, but have always thought better of it in the end.

The [hook call] Command

Two members of tcl-core have suggested that a hook call command would be useful to support plug-in architectures. hook call is like hook send, except that it would capture the return values of all observer bindings for use by the caller. This is an interesting notion; but I'm not sure how to get it right. In some use cases, it would be enough just to get a list of the return values. In other uses cases, the caller might want to know the observer, the return value, and the complete dictionary of return options.

This command could easily use the same table of bindings as hook send, but it wouldn't change the basic infrastructure particularly. Consequently, it can be easily added later, should this TIP be accepted.

Prototype Implementation

A prototype implementation is available at [1]. It is written in Tcl, and makes use of SQLite3. Provided that SQLite3 is available, the prototype implementation should work in both Tcl 8.5 and Tcl 8.6.

Note: The prototype is still for the old notifier API. I'll update it after the community has had a chance at this draft of the TIP.

Copyright

This document has been placed in the public domain.


Powered by TclThis is not necessarily the current version of this TIP.

TIP AutoGenerator - written by Donal K. Fellows