Tcl Source Code

TclOO Package
Login

Be aware that this "package" is really rather entangled with procedures, particularly for the implementation of methods. It also has some of its commands (self, next, nextto) bytecoded.

It is critically dependent on namespaces and (C-implemented) traces.


What is an object, a class, a method, and a call?

An object is a coupled command and namespace. Deleting either command or namespace will destroy the object. There's also another command coupled in (my) that's located inside the namespace by default; it can be deleted safely (though that will probably break your code if you're not expecting it) but it is the only command that is allowed to evaluate unexported methods. (Methods have flags that say whether they're exported — put in the public API as seen by the public command — or not.) Since commands can be renamed, if you NEED an identity for the object, use the namespace name; namespace cannot be renamed ever. (Internally, there's a sequence/epoch ID, but that isn't exposed.)

A class is an object that has an additional C structure attached behind the scenes that describes all the extra features of a TclOO class. (It's that structure which is why it is impossible to transform a class into a non-class or vice versa.) Classes inherit from oo::class which includes methods for instantiating the class; those methods just call into the relevant underlying API.

A method is very much like a Tcl command, but is not a command. The reason for this is that it takes an extra parameter that describes the call context (necessary for doing the next command right). There are several different method types in the core implementation, but the two flexible ones are standard procedure-like methods and forwarded methods.

Procedure-like methods
Procedure-like methods (made by method declarations) are the ones that work virtually identically to a normal Tcl procedure, and indeed they share a lot of implementation details: the big difference is that the reference to the call context is stored in a hidden field of the pushed stack frame; it's that field that the next and self commands look for, and it is why you need to be careful with stack frames when doing "clever stuff" with TclOO.
Forwarded methods
Forwards (made by forward declarations) are much like intra-interpreter aliases, and don't pass across the call context. However, the command that is forwarded to is resolved at call time with respect to the object instance's namespace; classes can forward to things that will be set up by their instances. This enables very many tricks, depending on how creative you get with command resolution. (Be aware that TclOO uses a non-empty namespace path by default.)

A call is invoked on an object (which could be a class) and names a method to call and provides a list of words to send in. The TclOO core starts by working out what sequence of method implementations this refers to; this is the call chain and is the main part of a call context. The call chain comes from linearising the inheritance graph (including all the mixins) and also includes handling any forwards. Even non-existing methods resolve to call chains, though those resolve to ones that include the unknown method handler (the root handler just generates a fancy error message, the one you usually see if you invoke a method that doesn't exist). Once a call context has been created (the other bits of it are things like indicators where in the call chain things are and a bunch of flags) the method is dispatched by calling the first implementation in the chain — which shouldn't be empty, though there's a special handler for that case — and passing in the context and arguments. The result of that is the result of the overall method call, and the implementation can dispatch to the later implementations on the chain if it chooses (e.g., via next). The memory for the call context is volatile and only lasts for as long as the call itself does, but the call chain (an expensive entity to compute) is shared; methods mostly don't see the call chain directly (it's hidden inside the context, which behaves like an opaque token).

Constructors and destructors use the same general principles (though the rules for filters and mixins are slightly different). Unlike with languages (like Java or C#), passing control along those call chains is not mandatory, merely advised. Code that doesn't have its constructors or destructors called may break or leak resources, but the effects will be technically well-defined. Any resources that must not be leaked still need to have appropriate exit handlers set up at the C level; TclOO makes no attempt to solve that for anyone.

Definitions

All a TclOO definition is is a script run in a very particular context. The context (which is actually very much like namespace eval) has a bunch of extra commands in it, and runs with a stack frame that has a reference to the entity being configured in a hidden field. The definition commands look for that hidden field, and do not work (with an error message) if it is absent. Procedures used to extend the collection of definitions need to use uplevel 1 to run any actual definition commands they want to use.

Class definitions use the oo::define command (which runs in the ::oo::define namespace) and instance definitions use the oo::objdefine command (which runs in the ::oo::objdefine namespace). The ::oo::define::self command flips from the class world to the instance world, allowing definitions to be put on the instance object itself.