################################################ # # # ## ## ###### ####### ## ## ## ## ## # # ## ## ## ## ## ### ## ## ## ## # # ## ## ## ## #### ## ## ## ## # # ## ## ###### ###### ## ## ## ## ### # # ## ## ## ## ## #### ## ## ## # # ## ## ## ## ## ## ### ## ## ## # # ####### ###### ####### ## ## ## ## ## # # # ################################################ The following paper was originally presented at the Third Annual Tcl/Tk Workshop Toronto, Ontario, Canada, July 1995 sponsored by Unisys, Inc. and USENIX Association It was published by USENIX Association in the 1995 Tcl/Tk Workshop Proceedings. For more information about USENIX Association contact: 1. Phone: 510 528-8649 2. FAX: 510 548-5738 3. Email: office@usenix.org 4. WWW URL: http://www.usenix.org ^L When is an object not an object? Mark Roseman Department of Computer Science, University of Calgary Calgary, Alberta, Canada T2N 1N4 (403) 220-3532 roseman@cpsc.ucalgary.ca Abstract This paper describes an approach to designing and building Tcl extensions that can be flexibly and dynamically changed using either Tcl or C. In particular, extensions having an object-based interface are considered. This extension approach seeks to avoid the "chasm" found in migrating code from Tcl to C as it matures by freely mixing Tcl and C to create an object's subcommands. The approach differs from traditional Tcl object frameworks in that it retains familiar mechanisms used to create new toplevel Tcl commands, and emphasizes extensions holding much of their data at the C level. A secondary goal is to illustrate how extension authors can encourage rich customization, by exposing object internals to change. To illustrate the technique, a simple data structure is extended to support sharing between multiple Tcl processes. Introduction The success of Tcl has been founded on its ease of extension, leading to a large number of high quality extensions. The core Tcl interpreter provides a single mechanism -- the library function Tcl_CreateCommand -- for extending the interpreter, whether directly to add a new command written in C, or indirectly, using "proc" to define a new command written in Tcl. While providing sufficient power to provide for extensions, the explosion of extension frameworks suggests the core mechanism does not necessarily make it easy to create the sorts of extensions developers want. In particular, the core mechanism provides no support for the ubiquitous "object-oriented" style found in extensions such as Tk, where a single Tcl command supports many methods or subcommands. Many of the object oriented extension architectures -- such as [incr Tcl] -- have arisen to make it easier to add new subcommands to objects. These approaches tend to result in objects which are used like other Tcl objects, but creating these objects is fundamentally different from creating toplevel Tcl commands. These architectures also provide limited support for creating extensions that are implemeted primarily at the C level. This paper describes a complementary approach, which leverages the familiarity and simplicity of the Tcl_CreateCommand approach to defining object oriented commands. While systems such as [incr Tcl] tend to emphasize extensions whose data and operations are primarily implemented at the Tcl level, the approach here is more suitable for extensions whose implementations are primarily at the C level. The goal here is to allow users of extensions -- even C-based ones -- to customize them as easily as they would [incr Tcl] extensions, using either C or Tcl, and in a manner analogous to creating toplevel Tcl commands. The lessons here are also applicable to [incr Tcl]-type extensions, suggesting how to design objects for easier customization. This is not presented as an extension itself -- the paper will argue why this is a poor idea -- but rather as a set of guidelines or considerations to keep in mind when writing extensions. Keeping in mind that many people find "meta" to be a four letter word, the paper is heavily grounded in a particular extension which uses the techniques described here. Terminology A quick note on my use of the word "object" is appropriate here. As alluded to in the title, there are differing uses of this term. Throughout the paper, "object" and "object oriented" will normally refer to objects in the Tcl sense [4], such as found in Tk. One toplevel Tcl command (e.g. "button") creates an object, which results in the creation of another Tcl command (e.g. ".mybutton") having subcommands (e.g. "invoke") which perform operations on the object. I'm not assuming anything about an object-oriented system in the C++ or Smalltalk sense, where we have other properties such as inheritance. Motivation This section examines some relevant work to better understand the approach taken in this paper. Current Tcl object frameworks are briefly examined to highlight their strengths and weaknesses. A case is made that the transition from Tcl to C code is far too difficult and heavyweight at the moment. Finally, some work in computational reflection suggests some promising strategies for exposing the internals of objects to customization. Why Allow Users to Customize Extensions? A basic tenet of this paper is that there are often cases where it is valuable to customize an extension. That no extension will suit every need should be obvious. Often however a user's needs can be better met by small changes or additions to an existing extension rather than coding from scratch. I would argue that extension authors can actively support this sort of customization, rather than leaving users with the often grim prospect of mucking in the extension's source code. Here are a few examples of customization scenarios: ¥ Can I change how [incr Tcl] inherits objects? ¥ Can a certain data structure be made persistant? ¥ Dump a Tk canvas as a GIF rather than Postscript? ¥ Have Tcl-DP use a different transport protocol? ¥ Make Tk work on a Mac? The traditional approach is all too often to either toss out or severely modify the extension's internals. The approach here suggests that with good design and open implementations, it may be possible to anticipate and provide for such future changes and new uses. Traditional Object Extensions A number of object extensions have appeared over the last couple of years as a way to create object oriented Tcl commands. For reasons of brevity, I'll focus on Michael McLennan's excellent [incr Tcl] system as an exemplar of the approach [3]. The system is modeled after the style of objects found in C++, where object classes are defined, containing state information (fields or instance variables) and behaviors (methods or member functions). Classes can inherit (subclass) from other classes, allowing changes or extensions to be made to a class. Once a class is defined, instances of the class can be created containing the fields and methods described by the class. Classes, their fields and methods, are described by Tcl scripts. The advantage to these frameworks is the added structure they add to Tcl programs, which is necessary for building larger programs. The frameworks add methods or subcommands through the class definition mechanism, which is somewhat more heavyweight than the traditional method of defining new Tcl commands. While still not preventing very interactive prototyping, it can be more difficult. As well, the frameworks do promote a different mental model of a program than found in standard Tcl. Current Tcl object frameworks emphasize systems whose data and methods are usually stored at the Tcl level, though some, such as the newest version of [incr Tcl], support defining methods in C. For Tcl-based extensions particularly, they provide a rich means of customization, though implementing it through the "different" paradigm of classes and inheritance. The Extension Chasm The other main approach to extensions is of course to "roll your own" using the core mechanisms directly provided by Tcl -- define toplevel commands at either the C or Tcl level. C level extensions tend to be better when there is a larger amount of possibly complex data or computation involved. Observations of Tcl development practice suggest that doing a C extension in this manner is particularly heavyweight. Extensions at this level have tended to be notoriously difficult to extend or customize -- a point returned to shortly. Additionally, C level extensions encapsulated in a toplevel "object oriented" command tend to be an "all or nothing" affair, where most, if not all of the extension is done in C at once. As many extensions seem to begin life as Tcl-based prototypes, it can be a daunting task to move from a Tcl-based implementation to a C based one, even when it becomes far too complex to realistically maintain in Tcl. Some of the "megawidget" extensions are good examples of this. Ideally, one would like to be able to more incrementally move an extension from Tcl to C, as performance and other requirements dictate. It is completely reasonable that an extension -- including one providing object oriented commands -- could be implemented in a fluid mixture of both Tcl and C, using both as appropriate. This level of integration, analogous to the smooth mixture of C and Tcl toplevel commands, is not found in current extension writing practice. Open Implementations There has been a great deal of work in the computational reflection community on better understanding the role of abstraction in software development. This community has been concerned with meta-level architectures in highly dynamic programming languages such as CLOS and Dylan. This section describes some of the tenets held by that community, in the hopes of understanding how to create more flexible, extensible and dynamic extensions in Tcl. Material here draws heavily from Kiczales [2]. Fundamentally, we use abstraction to manage complexity in systems. Under an object-oriented metaphor, objects are the method for representing abstraction. Objects reduce complexity by allowing their clients to deal with only the "necessary" functionality of the object, while hiding the details of the implementation. Conventional views of abstraction hold that a client need not and cannot care about the object's implementation, just its interface. The reflection community holds that this view of abstraction is not sufficient in practice. Kiczales uses the example of creating a view of a 100x100 cell spreadsheet object, by using the window system to create a sub-window for each cell. Though this is faithful to the abstraction provided by most, this solution is likely to be far too slow on most window systems. Important information about the design of a window system -- that it is optimized for a relatively small number of windows -- is hidden from the developer using the window system. The developer is forced to "work around" the abstraction to solve the problem, an all too common scenario. The abstraction fails by hiding relevant implementation internals behind its interface, making them inaccessible. This is not to say that abstraction has no value, but instead that it is important to be able to "get inside" the abstraction when necessary, for performance or other reasons. Rather than defining only a single interface to an abstraction, two interfaces can be defined. The second, an adjustment interface, allows for examining and changing the internal workings of the object, which are hidden from view under a traditional view of abstraction. While the first interface allows the developer to ignore details of the implementation, the second interface provides a recourse in the case when abstraction breaks down. Defining this adjustment interface is somewhat complementary to the process of actually implementing it. Object inheritance and subclassing is one method, but subclassing does not ensure a good adjustment interface. Computational reflection is founded on the premises that the design of the implementation should be as modular and well thought out as the design of the interface, and that an adjustment interface should be available to the abstraction's clients to permit examining and changing the implementation when necessary. Goals of this Work The remainder of this paper presents an approach to building Tcl extensions having an object-oriented interface that are easily customized by their users. The approach illustrates two main points: 1. An extension whose core data and operations exist largely at the C-level can be changed at least as easily as one implemented using a Tcl-based object framework. 2. Careful design is necessary to ensure an easily customized extension; this applies to extensions written using the approach here as well as with other approaches. In achieving these goals, the approach describes how to build extensions having the following properties: 1. Objects can be easily extended in either Tcl or C, by adding to or replacing existing methods. 2. Objects can be individually extended at runtime for greater customizing, i.e. extension is instance- based, not class-based. 3. The internals of objects are exposed and may be changed by users of an object, resulting in dramatic changes of behavior. 4. Extending objects should be done in a manner analogous to creating new toplevel commands. Again, the focus of this work is a set of design principles for building customizable extensions that have an object-oriented interface -- it is not itself an object-oriented extension. I believe the range of possibilities this approach applies to are far greater than could be encapsulated with a single extension. An Example Scenario To ground this approach in the concrete, the paper draws on an example found in GroupKit, a Tcl extension that helps developers create real-time groupware applications [5]. In real-time groupware, applications run across different machines, permitting, for example, a drawing program to be shared by users across a network, all contributing to a single drawing. In GroupKit, this is accomplished by having copies of the application running on each machine, exchanging messages with each other (using the Tcl-DP extension [6]). GroupKit uses a data structure called an "environment" (for historical reasons) to keep track of a lot of information such as what users are active in a session and what they are doing. Environments are hierarchical data structures where any node can hold either a value or have other nodes as children. Nodes are referred to by a key, using a "." as a hierarchy delimiter, e.g. "users.5.name". Environments bear a strong resemblance to Extended Tcl's keyed lists, which served as the basis for the earlier implementations. Environments are a very simple example of an object; they can be created and destroyed, and methods are provided to add, delete and inspect nodes in the environment. Table 1 summarizes some of the operations available in environments. In GroupKit, we wanted to be able to maintain this simplicity while permitting environments to be shared between GroupKit processes. Ideally, just by changing a node in the local copy of an environment, the change would be appropriately propagated to environments in the other processes. Under normal circumstances, application developers should not need to know how this occurs. We knew from our earlier work [1] that it would not be sufficient to provide a single method for doing concurrency control or replication. A wide variety of choices are possible, and these can have dramatic effects on the user interfaces of the highly interactive multi- user applications built with GroupKit. See Table 2 for examples of some of the customization possibilities. A better approach was to provide a core object that could be easily extended by either ourselves or application developers to support different strategies as needed for a particular application. Operation Description gk_env envName create an environment and its command envName set node value set the value of node in the environment to value envName get node get the value of a particular node; if the node has children, return a keyed list representing the structure of the subtree rooted at node envName delete node delete the indicated node, or subtree envName keys ?node? return the list of direct children of the given node envName option ?args..? get or set environment options, specified by key and value, the same as the environment's data envName destroy destroy the environment and its command Table 1. Core operations on environments. The Extension Approach This section describes how to design and build these open and extensible Tcl objects, using the GroupKit environments as an example of one possible implementation. Object Creation A single toplevel Tcl command (e.g. "gk_env") is defined in C and registered with the Tcl interpreter. When invoked with an object name, this command performs the following operations: 1. Create and initialize any necessary internal core data structures. 2. Create a table of subcommands (e.g. a hash table) and fill in with a set of default operations. 3. Register a toplevel Tcl command to handle the object's instance command. Defining Core Data and Operations Despite the possibility for potentially radical change, a core set of data structures and operations are usually provided. This does two things. First, it defines the base level capabilities of the object, which will likely be shared by all or most extensions made to the object. Second, this provides a default implementation, hopefully suitable for use by a number of extensions. For environments, the core data structure provided is a n-ary tree, where each node holds either a pointer to a value string or a pointer to a linked list of children, themselves nodes. A root tree is created, along with a node for holding a "data" subtree, and a node for holding an "option" subtree. Core operations including those necessary to get/set/delete values in either subtree. These are wrapped into "builtin" handlers for subcommands like "get" and "set" (which may be overridden, see below). Defining these data structures and primitive subcommands provides the necessary building blocks you'd need for extending environments. Fundamentally, these are the sorts of things any environment will want to do, though it might accomplish these operations in different ways. As well, this allows the possibility of extensions that replace the internal data representation if necessary, by building a new structure and specifying new primitives to replace the builtins. Subcommand Dispatch As mentioned above, when an object is created, one of the internal data structures it contains is a table of subcommands. The environment's instance command (analogous to a Tk widget command), searches through this table when an instance command is invoked to find a handler for the subcommand. Handlers can consist of either a Tcl script or a C function. For the former, a Tcl command string is created by appending the script handler with the name of the environment as well as any additional arguments passed by the caller. The resulting command is then executed via Tcl_Eval. For a handler implemented as a C function, the function is called directly, passing the original arguments, and the environment as the clientData. If a subcommand does not exist in the table, the subcommand dispatcher looks for the existence of a subcommand named "unknown" and will execute that if present. This allows for extensions such as the "implicit get/set" syntax extension described in Table 2. The subcommand dispatch process is illustrated by the following pseudo-code: cmd = FindHashEntry(env->cmds, argv[1]) if (cmd==NULL) cmd = FindHashEntry(env->cmds, "unknown") if (cmd==NULL) error if (cmd->type==C_SUBCMD) cmd->func(clientData, interp, argc, argv) else Tcl_Eval(interp, concat(cmd->script,argv)) Concurrency control none (default) * Changes (e.g. set, delete) affect only the local copy; changes are not reflected in copies of environments in other processes. no concurrency * Changes made locally are broadcast to other copies of the environment, but changes from multiple processes may arrive in different places in different orders, leading to inconsistent states [Note: for some groupware cases, this is perfectly acceptable]. centralized server * All local changes are sent to a central copy of the environment, which serializes the changes (guaranteeing consistency) and sends them back to all copies, at which point changes take effect. [Note: can be substantial time lag depending on network]. locking A portion of the environment must be locked before making changes, so a lock must be received before a change takes effect. [Note: potentially faster than centralized server if using the same part of the environment multiple times; locks can be implemented using many strategies]. optimistic locking Like locking, but immediately make the change under the assumption you'll probably get a lock. [Note: must be able to deal later on with having the data revert back to its original value if the lock is denied.] Notification none (default) * No notification when the environment changes global handler * A global (application-wide) handler is called to notify the application that the environment has changed, potentially as a result of operations in a remote environment. [Note: in GroupKit, this uses the same mechanism used by other events]. binding table* Bind event handlers to the environment directly, in a way similar to how events are bound to Tk widgets. The environment then deals with events directly. Other implicit get/set * A syntax extension whereby if the environment subcommand is not one of the recognized subcommands, it will attempt to map the command onto a get or set command, which is a very convenient shorthand and useful in prototyping, e.g. "env foo" maps to "env get foo" and "env foo bar" maps to "env set foo bar". ignore errors * An extension whereby operations like "get" or "keys" on non-existent nodes in the environment return an empty string rather than an error. [Note: potentially useful for prototyping, and in the case of GroupKit, a way to achieve backwards compatibility with some questionable earlier design choices]. Table 2. Potential extensions of environments. Extensions marked with a "*" have been implemented to date. Defining Tcl Subcommands Subcommands are specified as normal Tcl scripts and then added to the object. For example, one subcommand that might be built is an "exists" subcommand for environments, which takes a single node key and returns a 1 if a node with that key exists in the environment, and a 0 if not. Assuming a "get" subcommand already exists (one implementation is supplied as a builtin), this subcommand could be defined as follows: proc _gkenv_exists {env cmd key} { set result [{catch $env get $key}] if {$result==0} { return 1 # no error - exists } else { return 0 # error - doesn't exist } } The following code would be used to add this subcommand to an existing environment (e.g. "myEnv"): myEnv command set exists "_gkenv_exists" This command associates the "exists" subcommand with the Tcl script "_gk_env_exists". When invoked, e.g. via "myEnv exists foo", the _gk_env_exists proc will be called by the environment as follows: _gkenv_exists myEnv exists foo Defining C Subcommands Subcommands specified in C are defined in exactly the same way that toplevel Tcl commands are defined. The argument list passed to the subcommand handler is copied verbatim from the argument list passed to the toplevel instance command. The clientData parameter holds a pointer to the object's internal representation (i.e. its core data structure described earlier). We might define the "exists" subcommand from above instead in C as follows. Note that in this and other examples, error checking code has been omitted for purposes of clarity. int GkEnv_ExistsCmd(ClientData clientData, Tcl_Interp* interp, int argc, char *argv[]) { char *newArgs[3]; int result; Environment *env = (Environment*)clientData; Subcommand *subcmd = FindSubcommand(env, "get"); newArgs[0] = argv[0]; newArgs[1] = "get"; newArgs[2] = argv[2]; result = ExecSubcommand(env, interp, subcmd, 3, newArgs); if (result==0) Tcl_SetResult(interp, "1", TCL_STATIC); else Tcl_SetResult(interp, "0", TCL_STATIC); return TCL_OK; } The subcommand would be registered with the environment as follows: Env_AddSubcommand(env, "exists", GkEnv_ExistsCmd,NULL); Note that this command simply locates the "get" subcommand in the environment and executes it, completely analogous to the Tcl version. This has the advantage of working even if the implementation of the underlying data structure changes, requiring only the "get" subcommand be reimplemented if the data structure changes. This is generally preferable. However, the "get" operation can be expensive, particularly when retrieving a large subtree which must be converted into a keyed list representation. The following implementation for "exists" could be substituted that uses an internal procedure to get the node of a tree, but would require reimplementation if the underlying data structure changed: int GkEnv_ExistsCmd(ClientData clientData, Tcl_Interp* interp, int argc, char *argv[]) { Environment *env = (Environment*)clientData; EnvNode *node = Env_FindNode(env,argv[2]); if (node!=NULL) Tcl_SetResult(interp, "1", TCL_STATIC); else Tcl_SetResult(interp, "0", TCL_STATIC); return TCL_OK; } Manipulating Subcommands One of the key features of these objects is the ability to manipulate the available list of subcommands. This can be achieved at the C level by changing the Tcl hash table holding the commands (via Tcl library functions, or wrappers like Env_AddSubcommand). At the Tcl level, a "command" subcommand is provided, described in Table 3. The "command rename" subcommand is especially useful, since it allows you to wrap an exiting subcommand to provide additional functionality. This is equivalent to extending an inherited method in a full object oriented system. For example, to generate an event when a node is deleted from the environment, the following code could be used: myEnv command rename delete _olddelete myEnv command set delete notifyDelete proc notifyDelete {env cmd node} { $env _olddelete $node generateDeleteEvent $node } Having the convention of an underscore to preface "internal" commands is useful. Objects can also inspect their own commands to generate new names via "command list". This allows such commands to be composed, as will be illustrated in the later section on packaging objects and extensions. Note that since objects can manipulate even their built- in commands it is possible to "lock" an object (preventing changes at the Tcl level) with the following: myEnv command delete command envName command set subcmd proc Set the subcommand handler for subcmd to proc envName command get subcmd Return the Tcl script for the subcommand subcmd, or for subcommand handlers written in C envName command delete subcmd Remove the subcommand subcmd from the object envName command list List the subcommands in the object envName command rename oldcmd newcmd Register the same subcommand handler for newcmd as is registered for oldcmd Table 3. Operation of "command" subcommand. Packaging an Object and Default Extensions It is useful to package together an object and a set of optional extensions. For example, in GroupKit we provide a command called "gk_newenv" which invokes "gk_env" internally. Depending on options passed to "gk_newenv", different commands are added to the environment. For example, application developers can specify "-notify" or "-share" (or both) for an environment. Typically, sets of changes are packaged together. This section walks through an example. The following Tcl procedure is the normal procedure invoked to create an environment. It parses through the arguments looking for "-notify" and "-share" flags, and picks out the name of the environment from the end of the argument list. It creates the environment, and then calls routines to add in notification and sharing if the appropriate flags are set. proc gk_newenv {args} { set notify no; set share no foreach i $args { if {$i=="-notify"} {set notify yes} if {$i=="-share"} {set share yes} } set env [lindex $args \ [expr [llength $args]-1]] gk_env $env if {$notify=="yes"} \ {_gkenv_initNotify $env} if {$share=="yes"} \ {_gkenv_initShare $env} } Next is the "notification" extension, which replaces the set and delete commands with routines that generate events in addition to making the requested changes. Note that this package also adds an internal "_notify" subcommand, which may be used by other extensions to do notifications if desired. These extensions can detect the presence of this subcommand by inspecting the list of available subcommands. It also allows new methods of notification to be devised, without repatching the set and delete commands. proc _gkenv_initNotify env { $env command rename set _set $env command set set _gkenv_notifySet $env command rename delete _delete $env command set delete _gkenv_notifyDel $env command set _notify \ _gkenv_genEvent } proc _gkenv_notifySet {env cmd node val} { $env _set $node $val $env _notify set $node } proc _gkenv_notifyDel {env cmd node} { $env _delete $node $env _notify delete $node } proc _gkenv_genEvent {env cmd event node} { # however notifications are done... } Next is the "sharing" extension, which implements a simple form of sharing that ignores concurrency control. Changes in a local copy of the environment are echoed in remote copies of the environment. The GroupKit command "gk_toAll" command is used to execute a command on all the GroupKit processes. proc _gkenv_initShare env { $env command rename set _doset $env command set set _gkenv_shareSet $env command rename delete _dodelete $env command set delete _gkenv_shareDel } proc _gkenv_shareSet {env cmd node val} { gk_toAll $env _doset $node $val } proc _gkenv_shareDel {env cmd node} { gk_toAll $env _dodelete $node } The current version of GroupKit provides six different extensions to the core environment data structure, many of them reusing not only the implementation of the core environment, but also the implementation of other extensions. Due to the implementation structure of the core environments, the amount of code to implement each addition is trivial. Defining packages of extensions in this way is similar to the use of "mixin" classes in C++. The advantage here is that to allow developers to freely intermix n different customizations of the basic object, there is no need to create 2n instantiable classes. Extensions to basic objects are composed rather than inherited here. The approach here is arguably cleaner and easier to understand than multiple inheritance. Conclusions This paper has presented an approach to building Tcl object-oriented extensions that can be easily extended in either Tcl or C. The approach brings a level of customization usually found only in Tcl-based extensions to those implemented largely in C. The description of open implementations and the case study should suggest ways that extensions built using either approach can be made more customizable. Rather than relying on a different paradigm, the implementation of these extensions reflects both the familiar structural aspects and the high level of dynamic programming available when creating top level Tcl commands. This lightweight approach to customization is intended to complement the more heavyweight approach found in conventional Tcl object frameworks. Ideas from computational reflection were evidenced in the approach, which exposes much of the internal object representation to inspection and change, yet does so in a controlled way so that casual users of the objects need not be concerned with the internal complexity. The techniques described here were illustrated using GroupKit's environments, showing how a simple data structure was extended to support notification and data sharing between replicated processes. The strength of Tcl is its simplicity of extension and customization; using the approach described here can help extension writers capture the same simplicity in their own work. Acknowledgements This paper benefited from feedback provided by Rob Kremer, Earle Lowe and Saul Greenberg, and discussions with Michael McLennan. Ted O'Grady and Paul Dourish, both of whom would cringe to find their names in a paper about Tcl, convinced me of the value of meta-level architectures. Financial support was provided by the National Science and Engineering Research Council and Intel Corporation. References 1. Greenberg, S. and Marwood, D. (1994). Real Time Groupware as a Distributed System: Concurrency Control and its Effect on the Interface. Proceedings of CSCW '94. 2. Kiczales, G. (1992). Towards a New Model of Abstraction in the Engineering of Software. Proceedings of IMSA '92. 3. McLennan, M. (1993). [incr Tcl] -- Object- Oriented Programming in Tcl. Proceedings of the 1993 Tcl workshop. 4. Ousterhout, J. (1994). Tcl and the Tk Toolkit. Addison-Wesley, pp. 283-284. 5. Roseman, M. (1993). Tcl/Tk as a basis for groupware. Proceedings of the 1993 Tcl workshop. 6. Smith, B., Rowe, L. and Yen, S. (1993). Tcl Distributed Programming. Proceedings of the 1993 Tcl workshop.