Thoughts on implementing OO in tcl

by MS, an absolute beginner


Wish 0: Instance variables should be *regular* tcl variables, and should allow handling as such (set, lappend, trace, ...). In the same vein, instance procs should be *regular* tcl procs. In general, the OO approach should stay fully compatible with tcl. Ideally there is no new parser or compiler for OO, everything is paresd/compiled by tcl in the usual manner. Also, if somebody defines a new Tcl_Obj, the OO system can *automatically* use it for variable values without having to define extra handling commands - whatever is implemented in tcl works in OO-tcl too (up to possible name collisions, of course ...)

Wish 1: OO should not add a (significant) performance penalty: you need fast method dispatching

Wish 2: Creation and destruction of objects should be a quick operation, in order to allow/encourage the use of short lived objects. To *my* mind, this is of secondary importance; I might be *very* wrong on this though, I just have no experience on GUI programming where I imagine that issue could jump to the foreground.

Fact 1: An object is a "namespace": it gives a special meaning to variable and command names. The usual association of namespaces to classes is beyond me - even for static OO, objects of a same class give a different meaning to names.

So, it seems that it makes sense to use tcl's namespace facilities to implement objects. This is (as I see it) the unifying feature in several of the proposed pure-tcl OO approaches. However:

  • Tcl provides relatively good facilities for command inheritance, via namespace import; but it does have its problems (addressed below)
  • Tcl does not provide adequate facilities for variable inheritance (as discussed below). Variables will probably have to be *copied* at object creation time ...
  • Tcl namespaces are relatively heavyweight

Fact 2: A *user friendly* object is also a command - as evidenced in just about every OO implementation.


Issues with "namespace import"

"namespace import" is a good way to inherit methods: the method will not be recompiled by the inheritor, the bytecode is usable as is; it is relatively lightweight; it provides for a nice dynamic updating of object methods (change the code at the origin, every inheritor is updated automatically and only the first user recompiles!)

On the other hand, a method to be imported into a different namespace has to be carefully programmed to insure that the methods of the intended object are used. The only nice way I see to do that implies:

  1. the method has to receive the name of the object to which it has to apply
  2. every command within the method should be fully qualified: either it is a global command, or else a method of the object being processed, or a method of some other object determined at compile time.

So: either the (inheritable) method programmer has to be extra careful, or else the system has to provide its own proc parser for inheritable methods. That is quite a job; and I for one do not like the idea ...

If done from C: one could (almost) force a correct coding of instance procs by defining a special command-name-resolver for namespaces that are objects, so that every command reference is global.

An alternative would be to wrap the whole body in an "uplevel" command - slow as mail, the body will never be compiled. Or else, inherited procs will have to be *copied* to the new object (I haven't thought about commands written in C yet); this implies a slower object creation, and a compilation at the first call of each method.

Linking to the instance variables of the intended object can also be done using "global" on the fully qualified name (or "upvar", depending on the implementation of the OO system). "variable" is out of the question, it will link to the variables of the object where the proc was originally implemented.


Issues with variables

There is no equivalent to "namespace import" for variables at the tcl level (you can do it in C, but keep reading).

Even if there were, the semantics of tcl do not allow a similar mechanism:

  • Commands are "imported" *only* for "read access" (read execution); if you write to them ("proc", "rename", "import" again), the modification does *not* affect the original.
  • If you link variable a to variable b (at the C level), you obtain a *full* linking: the names a and b are fully equivalent. You cannot update a without changing b, you cannot trace a (the trace will be on b!), ... This cannot be solved at the variable-name-resolver level: the resolver only receives a request for an address, and *not* the intended use of that address. It cannot therefore give different addresses for (read, write, create, unset) accesses.

Therefore, variables have to be *copied* from ancestors at object creation time. This precludes a (simple, fast, ...) dynamic updating of "class variables", every variable is an instance variable.

Actually, my knowledge and imagination failed me; dynamic updating can be implemented with read traces on the objects variables ...


wdb -- the variable's equivalent to "namespace import" is "upvar #0 yourname myname" where write to "myname" affects "yourname" as well.


Commentary

DKF: TclOO satisfies quite a few of these wishes, but not all. Its objects are namespaces. Its variables are ordinary namespace variables. Its parsing (of configuration descriptions) is done through just evaluating the description as a script. Its dispatch is about as fast as I can make it (slightly slower than for a proc, but not very much more) and its creation/destruction is pretty speedy too (though inevitably slower as memory does need to be allocated).

The one I had to drop was making methods be commands. The problem is that to make method dispatch work nicely, you need to pass a piece of context around that is a reference to the description of the current method dispatch (holding information like the identity of the object on which it was invoked). This information has a proper lifespan that is linked to the stack, not the interpreter or the object, so things get very messy if you try to wedge it into any of the existing signature (it's not a Tcl_Obj so it isn't an argument and the ClientData is used for other purposes; poking in there is very problematic). XOTcl solved this originally by using an extra stack frame, but that hugely complicates things for method authors (upvar and uplevel are heavily impacted); in TclOO I instead pass the information as an extra argument in the C type signature of method implementations. This formally means that commands cannot be methods, but doing this key change turned TclOO from a messy pipe-dream into solid reality.

We do allow methods to be forwarded to commands (a process that really just strips the "method-ness" and calls the command). They need to be declared (or constructed at runtime by an unknown method handler) but that's pretty easy.