dkfobj (v1.5) Documentation


SYNOPSIS
  class subcommand classOrObjectName ?args...?
classname
classname -> propname ?op args...?
classname methodname ?args...?
objectname
objectname -> propname ?op args...?
objectname methodname ?args...?

DESCRIPTION

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.

In More Detail, and Some Reasoning Behind the Extension

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.


The Details

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.)

The class command

class define classname deflist ?deflist ...?
Creates a new class called classname whose definition is built up from the concatenation of the deflist arguments. See below for details...
class list classes ?pattern?
Lists all the classes whose names match the supplied pattern, or all of them if no pattern is provided.
class list objects classname ?pattern?
Lists all the objects of class classname whose names match the supplied pattern, or all of them if no pattern is supplied.
class list methods classOrObject ?pattern?
Lists all the methods of the given class or object (named classOrObject) that match the supplied pattern, or all of them if no pattern is supplied.
class list properties classOrObject ?pattern?
Lists all the properties of the given class or object (named classsOrObject) that match the supplied pattern, or all of them if no pattern is supplied.
class info class classname
Returns a pair stating whether classname is a virtual class (the first value - a boolean), and what the superclasses of the class are (the second value - a list of class names.)
class info object objectname
Returns a pair stating what the class of objectname is (the first value - a class name), and what constructor was used to create the object (the second value - a constructor method name.)
class info method classOrObject methodname
Returns a pair stating what style of method is described by methodname (the first value - a value indicating the access control permissions on the method and whether the method is a constructor) and what command () is used to implement the method (the second value - a command name. Typically, though not necessarily, a procedure.)
class info property classOrObject propertyname
Returns a triple stating what style of property is described by propertyname (the first value - a value indicating the access control permissions on the property), what was the last object or class to write the property (the second value - a class or object name. This is blank if the property has never been written), and whether the property is defined (the third value - a boolean which is only useful for objects where the default value for a property is taken from the defining class, so the value itself might not yet exist in the object itself.)
class exists classname
Returns whether the class named classname currently exists.
class of objectname
Returns the class of the object named objectname.

Creating a class

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 and constructors

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.

Property manipulation

classOrObject -> propertyName
Read from the property called propertyName of the class or object called classOrObject.
classOrObject -> propertyName = value
Write value to the property called propertyName of the class or object called classOrObject.
classOrObject -> propertyName append string ?string ...?
Append the strings to the property called propertyName from the class or object called classOrObject.
classOrObject -> propertyName exists
Check if a property called propertyName exists in the class or object called classOrObject.
classOrObject -> propertyName incr ?intValue ...?
Add the list of integer values to the property called propertyName from the class or object called classOrObject. If no values are present, then add 1.
classOrObject -> propertyName lappend value ?value ...?
Add the values given onto the end of the list in the property called propertyName from the class or object called classOrObject. Each value will form a separate element of the new list.
classOrObject -> propertyName link ?varName?
Query, set or remove the link between the property called propertyName from the class or object called classOrObject and the global variable called varName. If no varName is supplied, then the name of the variable that the property is currently linked to is returned. If the supplied variable name is an empty string, then the current link is removed. Otherwise, a link is created between the named variable and the given property.
classOrObject -> propertyName trace read ?script?
Query, set or remove a script to be run every time the property called propertyName from the class or object called classOrObject is read from. If no script is supplied, then the current script is returned. If the supplied script is an empty string, then the current script is deleted, and otherwise the current script is set to the supplied value. The script is called before the value is actually read from the property, so any updates made by the script will cause a new value to be used instead. If the script in question generates an error when called, the read of the property will fail.
classOrObject -> propertyName trace write ?script?
Query, set or remove a script to be run every time the property called propertyName from the class or object called classOrObject is written to. If no script is supplied, then the current script is returned. If the supplied script is an empty string, then the current script is deleted, and otherwise the current script is set to the supplied value. The script is called after the property has been written, though if the script in question generates an error when called, the write of the property will fail and the property will be reset so it contains the value it had previously.
classOrObject -> propertyName unset
Delete the property called propertyName from the class or object called classOrObject.

Bugs

In the extension

In this document


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