OTcl Tutorial (Version 0.96, September 95)

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.

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 the info 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 the Bagel 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 $self next $args
}
% 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 self is 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 the set method, can now be manipulated through the local variable toasted.

We invoke the toast instproc on bagels in the same way we use the info 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 class SpreadableBagel 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 that SpreadableBagel does indeed inherit from Bagel, and further that it also inherits from Object. Object embodies the basic functionality of all objects, from which new classes inherit by default. Thus Bagel inherits from Object directly (we didn't tell the system otherwise) while SpreadableBagel inherits from Object indirectly via Bagel.

The creation syntax, with its "-superclass", requires more explanation. First, you might be wondering why all methods except create 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 the Object class, and is why the Bagel init instproc 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} {
  $self set bites $n
}
% SpreadableBagel abagel -size 12
abagel
% abagel set bites
12

We need to add methods to spread toppings to SpreadableBagel, 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 $self next $args
}
% SpreadableBagel instproc spread {args} {
  $self instvar toppings
  set toppings [concat $toppings $args]
  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 to next 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 Bagel class is found and invoked. Eval is being used only to flatten the argument list in args. When next is called again in Bagels init instproc, the init method on Object 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 next allows 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 info option. 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.