OTcl Tutorial (Version 0.96, September 95) (original) (raw)
This tutorial is intended to start you programming in OTcl quickly, assuming you are already familiar with object-oriented programming. It omits many details of the language that can be found in the reference pages Objects in OTcl and Classes in OTcl. It also doesn't mention the C API or describe how to autoload classes.
Comparison with C++
To the C++ programmer, object-oriented programming in OTcl may feel unfamiliar at first. Here are some of the differences to help you orient yourself.
- Instead of a single class declaration in C++, write multiple definitions in OTcl. Each method definition (with instproc) adds a method to a class. Each instance variable definition (withset or via instvar in a method body) adds an instance variable to an object.
- Instead of a constructor in C++, write an init instproc in OTcl. Instead of a destructor in C++, write a destroyinstproc in OTcl. Unlike constructors and destructors, init and destroy methods do not combine with base classes automatically. They should be combined explicitly with next.
- Unlike C++, OTcl methods are always called through the object. The name self, which is equivalent to this in C++, may be used inside method bodies. Unlike C++, OTcl methods are always virtual.
- Instead of calling shadowed methods by naming the method explicitly as in C++, call them with next. nextsearches further up the inheritance graph to find shadowed methods automatically. It allows methods to be combined without naming dependencies.
- Avoid using static methods and variables, since there is no exact analogue in OTcl. Place shared variables on the class object and access them from methods by using $class. This behavior will then be inherited. For inherited methods on classes, program with meta-classes. If inheritance is not needed, use proc methods on the class object.
Programming in OTcl
Suppose we need to work with many bagels in our application. We might start by creating a Bagel class.
% Class Bagel Bagel
We can now create bagels and keep track of them using theinfo method.
% Bagel abagel abagel % abagel info class Bagel % Bagel info instances abagel
Of course, bagels don't do much yet. They should remember whether they've been toasted. We can create and access an instance variable with the set method. All instance variables are public in the sense of C++. Again, the info method helps us keep track of things.
% abagel set toasted 0 0 % abagel info vars toasted % abagel set toasted 0
But we really want them to begin in an untoasted state to start with. We can achieve this by adding an init instproc to theBagel class. Generally, whenever you want newly created objects to be initialized, you'll write an init instproc for their class.
% Bagel instproc init {args} { $self set toasted 0
eval selfnextself next selfnextargs } % Bagel bagel2 bagel2 % bagel2 info vars toasted % bagel2 set toasted 0
There are several things going on here. As part of creating objects, the system arranges for init to be called on them just after they are allocated. The instproc method added a method to the Bagel class for use by its instances. Since it is called init, the system found it and called it when a new bagel was created.
The body of the init instproc also has some interesting details. The call to next is typical for init methods, and has to do with combining all inherited init methods into an aggregate init. We'll discuss it more later. The variable called selfis set when a method is invoked, and contains the name of the object on behalf of which it is running, or bagel2 in this case. It's used to reach further methods on the object or inherited through the object's class, and is like this in C++. There are also two other special variables that you may be interested in,proc and class.
Our bagels now remember whether they've been toasted, except for the first one that was created before we wrote an init. Let's destroy it and start again.
% Bagel info instances bagel2 abagel % abagel destroy % Bagel info instances bagel2 % Bagel abagel abagel
Now we're ready to add a method to bagels so that we can toast them. Methods stored on classes for use by their instances are called instprocs. They have an argument list and body like regular Tcl procs. Here's the toast instproc.
% Bagel instproc toast {} { $self instvar toasted incr toasted if {$toasted>1} then { error "something's burning!" } return {} } % Bagel info instprocs init toast
Aside from setting the toasted variable, the body of the toast instproc demonstrates the instvar method. It is used to declare instance variables and bring them into local scope. The instance variable toasted, previously initialized with theset method, can now be manipulated through the local variabletoasted.
We invoke the toast instproc on bagels in the same way we use theinfo and destroy instprocs that were provided by the system. That is, there is no distinction between user and system methods.
% abagel toast % abagel toast something's burning!
Now we can add spreads to the bagels and start tasting them. If we have bagels that aren't topped, as well as bagels that are, we may want to make toppable bagels a separate class. Let explore inheritance with these two classes, starting by making a new classSpreadableBagel that inherits from Bagel.
% Class SpreadableBagel -superclass Bagel SpreadableBagel % SpreadableBagel info superclass Bagel % SpreadableBagel info heritage Bagel Object
More options on the info method let us determine thatSpreadableBagel does indeed inherit from Bagel, and further that it also inherits from Object. Objectembodies the basic functionality of all objects, from which new classes inherit by default. Thus Bagel inherits fromObject directly (we didn't tell the system otherwise) whileSpreadableBagel inherits from Object indirectly viaBagel.
The creation syntax, with its "-superclass", requires more explanation. First, you might be wondering why all methods exceptcreate are called by using their name after the object name, as the second argument. The answer is that create is called as part of the system's unknown mechanism if no other method can be found. This is done to provide the familiar widget-like creation syntax, but you may call create explicitly if you prefer.
Second, as part of object initialization, each pair of arguments is interpreted as a (dash-preceded) procedure name to invoke on the object with a corresponding argument. This initialization functionality is provided by the init instproc on theObject class, and is why the Bagel initinstproc calls next. The following two code snippets are equivalent (except in terms of return value). The shorthand it what you use most of the time, the longhand explains the operation of the shorthand.
% Class SpreadableBagel SpreadableBagel % SpreadableBagel superclass Bagel
% Class create SpreadableBagel SpreadableBagel % SpreadableBagel superclass Bagel
% Class SpreadableBagel -superclass Bagel SpreadableBagel
Once you understand this relationship, you will realize that there is nothing special about object creation. For example, you can add other options, such as one specifying the size of a bagel in bites.
% Bagel instproc size {n} { selfsetbitesself set bites selfsetbitesn } % SpreadableBagel abagel -size 12 abagel % abagel set bites 12
We need to add methods to spread toppings toSpreadableBagel, along with a list of current toppings. If we wish to always start with an empty list of toppings, we will also need an init instproc.
% SpreadableBagel instproc init {args} { $self set toppings {} eval selfnextself next selfnextargs } % SpreadableBagel instproc spread {args} { $self instvar toppings set toppings [concat toppingstoppings toppingsargs] return $toppings }
Now the use of next in the init method can be further explained. SpreadableBagels are also bagels, and need their toasted variable initialized to zero. The call tonext arranges for the next method up the inheritance tree to be found and invoked. It provides functionality similar to call-next-method in CLOS.
In this case, the init instproc on the Bagelclass is found and invoked. Eval is being used only to flatten the argument list in args. When next is called again inBagels init instproc, the init method onObject is found and invoked. It interprets its arguments as pairs of procedure name and argument values, calling each in turn, and providing the option initialization functionality of all objects. Forgetting to call next in an init instproc would result in no option initializations.
Let's add a taste instproc to bagels, splitting its functionality between the two classes and combining it with next.
% Bagel instproc taste {} { $self instvar toasted if {$toasted == 0} then { return raw! } elseif {$toasted == 1} then { return toasty } else { return burnt! } }
% SpreadableBagel instproc taste {} { $self instvar toppings set t [$self next] foreach i $toppings { lappend t $i } return $t }
% SpreadableBagel abagel abagel % abagel toast % abagel spread jam jam % abagel taste toasty jam
Of course, along come sesame, onion, poppy, and a host of other bagels, requiring us to expand our scheme. We could keep track of flavor with an instance variable, but this may not be appropriate. Flavor is an innate property of the bagels, and one that can affect other behavior - you wouldn't put jam on an onion bagel, would you? Instead of making a class heirarchy, let's use multiple inheritance to make the flavor classes mixins that add a their taste independent trait to bagels or whatever other food they are mixed with.
% Class Sesame Sesame % Sesame instproc taste {} { concat [$self next] "sesame" } % Class Onion Onion % Onion instproc taste {} { concat [$self next] "onion" } % Class Poppy Poppy % Poppy instproc taste {} { concat [$self next] "poppy" }
Well, they don't appear to do much, but the use of nextallows them to be freely mixed.
% Class SesameOnionBagel -superclass {Sesame Onion SpreadableBagel} SesameOnionBagel % SesameOnionBagel abagel -spread butter % abagel taste raw! butter onion sesame
For multiple inheritance, the system determines a linear inheritance ordering that respects all of the local superclass orderings. You can examine this ordering with an infooption. next follows this ordering when it combines behavior.
% SesameOnionBagel info heritage Sesame Onion SpreadableBagel Bagel Object
We can also combine our mixins with other classes, classes that need have nothing to do with bagels, leading to a family of chips.
% Class Chips Chips % Chips instproc taste {} { return "crunchy" } % Class OnionChips -superclass {Onion Chips} OnionChips % OnionChips abag abag % abag taste crunchy onion
Other Directions
There are many other things we could do with bagels, but it's time to consult the reference pages. The OTcl language aims to provide you with the basic object-oriented programming features that you need for most tasks, while being extensible enough to allow you to customize existing features or create your own.
Here are several important areas that the tutorial hasn't discussed.
- There is support for autoloading libraries of classes and methods. See OTcl Autoloading for details.
- There is a C level interface (as defined by otcl.h) that allows new objects and classes to be created, and methods implemented in C to be added to objects. See OTcl C APIfor details.
- Classes are special kinds of objects, and have all of the properties of regular objects. Thus classes are a convenient repository for procedures and data that are shared by their instances. And the behavior of classes may be controlled by the standard inheritance mechanisms and the class Class.
- Methods called procs can be added to individual object, for sole use by that object. This allows particular objects to be hand-crafted, perhaps storing their associated procedures and data.
- User defined methods are treated in the same way as system provided methods (such as set and info). You can use the standard inheritance mechanisms to provide your own implementation in place of a system method.
- There are several other system methods that haven't been described. array gives information on array instance variables, unset removes instance variables, there are further info options, and so forth.