|
The class command is used to create and manipulate classes (you know, as in objects!) Classes are collections of operations (called methods) and variables (called properties) together with some extra stuff so that when the class is deleted, its associated variables are deleted as well. Furthermore, each of these classes can inherit definitions from other classes, allowing you to build a system bit-by-bit.
In addition to that, you can also create instances of the class (called objects) that are allowed to make modifications to the properties that will only affect themselves and not the other objects of the class. Like classes, the properties of the objects are deleted when the object itself is deleted.
This is all very well, you might say, but why would you have both classes and objects if they both have the same arrangement of properties? The difference between the two is really that each object takes the default values for its properties from its class, but that any attempt to assign to any property in the object creates a local copy within the object so that other objects of the class do not see the change.
Methods are defined through rewriting in terms of a procedure with an extra argument that specifies the class or object name. This allows methods to be written using exactly the same syntax as everyone is already used to, levering off the substantial support for procedures built into Tcl, as well as allowing the easy sharing of methods between classes.
Inheritance is performed through another neat trick: the method and property definitions of the parent class are simply copied from the parent, and a note is made in the parent so that should it get deleted, the child class will also get deleted.
Why did I write this extension? Because I needed OO
capabilities in Tcl7.6 (what I was working with) to cope with a
large project I was working on, and I hated the syntax used by [incr
tcl]
. The fact that it required me to use a patched
version of Tcl7.6 didn't help either, as I already had enough
trouble keeping all my sources in synch. This became even more
imperative when I switched to Tcl8.0 which Itcl hadn't been
ported to at the time (and at the time of writing about 16 moths
later, a version still hasn't been released to the best of my
knowledge) so I took my hacked up mess, worked out a more regular
syntax, and coded the whole lot straight as a new extension (I
knew from previous experience that an extension that was going to
be on as many critical paths within my application as this one
would need to be written in C or it wouldn't be able to go nearly
fast enough.)
To my great surprise, it turned out to run substantially
slower than the Tcl7.6 version when running the same (well,
syntactically converted) code, instead of going at the
substantially increased speeds you would tend to expect from
having a bytecode compiler to hand. After a load of
investigation, it turned out that the problem was due to an
unfortunate interaction between the commands I was generating to
be executed (built up as lists so that I could both be sure of
the quoting, and so that the compiler could take advantage of
this and go directly to executing the procedures in question) and
Tcl_EvalObj()
which had different ideas (it
converted everything not already compiled into a string before
compiling it, despite the fact that this is a useless and
expensive operation when the script in question is guaranteed to
be a well-formed list.) A chunk of hacking later, and I had
created my own backdoor round this that would grab the command
out of the Tcl_CmdInfo
structure and execute the
command directly, getting a massive speedup especially in the
case where you are slinging a long list that has never been
converted to a string, and never ought to. It turns out that this
works in virtually all cases, and the cases where it doesn't do
not turn up in practise as you tend not to define methods as
being performed directly by one of the commands that is compiled
directly to bytecode (like expr
, if
, while
,
etc.)
The work above describes the main technology behind this extension which is probably as close to being bug-free as I can reasonably make it by myself. I've certainly traced through it more than enough times with tools like the thoroughly excellent purify :^) The rest of it is merely bells and whistles, with the code to link an object or class property to a Tcl variable is probably the most interesting of those bells, as it greatly simplifies the use of Tk widgets in the process of manipulating an object.
Note that all class names must not have any trailing digits, as those names are reserved for objects (whose names are derived from the respective classes by appending digits so that a unique name is formed.) There are no restrictions on property and method names other than that they contain no embedded NULLs and must not be the empty string (not onerous restrictions, I'm sure you'd agree.)
When classes are created (using class define), their definition is provided by the sequence of deflist (definition list, of course!) arguments. The effect is almost that of concatenating the arguments into a single definition list, except that an additional restriction is made in that none of the definition phrases should be split between definition lists. I have found in my own use that this is not normally a problem, and having the ability to take a sequence of definition lists created using different styles of quoting and combine them into one overall definition list is very useful where you want to create part of the definition using another command.
Each of the definition lists is just that; a list. It has no internal formatting (thus, it didn't require a complex parser.) Each of the phrases that makes up a definition list consists of one or more words, and the first of those words must be one of a set of keywords that says what the definition phrase means.
The various definition phrases are:
- virtual
- Marks the class as a virtual (non-instantiable) class
- inherit className
- Copy all the class definition from the class className into the current one (except for the virtual-class status flag, of course!)
- method methodName implementingCommand
- Define a method (using the current method style) with name methodName that is implemented by the command implementingCommand.
- property propertyName initialValue
- Define a property (using the current property style) with name propertyName and initial value initialValue.
- constructor:
- Set the current method style to be "constructor" so that subsequent methods will be constructors. Constructors are only callable on classes, and always pass a new object to their implementing command instead of the name of the class which they were called for.
- class:
- Set the current method and property styles to be "class" so that subsequent methods will be class-only methods (not callable on objects) and subsequent properties will be class-only properties (not readable or writable from objects.)
- object:
- Set the current method and property styles to be "object" (this is the default style for properties) so that subsequent methods will be object-only methods (not callable on classes) and subsequent properties will be object properties (readable and writable from objects who will get their own copy when they write the property, but read-only from classes.)
- shared:
- Set the current method and property styles to be "shared" (this is the default style for methods) so that subsequent methods will be callable by both classes and objects (though with a different first argument in each case, of course) and subsequent properties will be readable and writable by both the class and the class's objects, with all of them sharing the same property (actually part of the class) so that updates made by one object affect all its siblings and its class as well (though none of the classes previously derived from the class. That process is a complete copy, as if the definition for the class as it stood at that point was copied directly into the definition list for the child class.)
Methods (of which constructors are just a special kind) are implemented through command rewriting. This works by arranging that the implementing command will be called every time the method is invoked with the first argument to the command always being the name of the class or object on behalf of which the method was invoked. The other arguments to the command remain unchanged.
Thus, if you had a class foo with the definition:
method Bar {the quick brown fox}
a call to the command:
foo Bar spong wibble
would get rewritten as:
{the quick brown fox} foo spong wibble
Note that the method definition phrase only specifies the name of the command that gets used to implement the method, and not any extra arguments that such a command would need. I feel that it is better to use a wrapper command that looks up the extra arguments based on the name of the calling class or object in the rare cases where you need such functionality, especially since this requirement allows the throwing of the method to the implementing command much quicker in the cases where no such extra arguments are needed.
All objects are built by calling a constructor method of the class that defines what the object will be able to do, though you should bear in mind that all (non-virtual) classes have a built-in null-argument constructor (invoked by calling the class with no extra arguments at all) so it is not necessary to create a constructor yourself in order to make a class usable. For all other constructors though, the calling of the constructor is different to the calling of any other method in that although it is called on the class, the first argument will be the name of the object created, and not the name of the calling class. In all other respects, constructors are the same as any other method.
There is one other special method for classes and two for objects. For backward compatability with earlier systems, the null-argument method for objects is the same as class of theObject (i.e. it returns the class of the object in question) and the method -> is the property access method for both classes and objects, and it is documented below.
This document is Copyright © 1998 Donal K.
Fellows.
Permission is given to redistribute this document
in electronic form for any purpose so long as this document is
not modified in any way. Furthermore, a printed copy may be made
for personal or not-for-profit use. Any other use of this
document requires the explicit permission of the author, (though
unlike many people on the 'net, the author is quite likely to
give his permission in those cases. Ask and you may well be
pleasantly surprised.)
Donal K. Fellows, Bradford, U.K. | Saturday 9th May, 1998 |