Introducing Tcl 8.7 Part 9: More TclOO

Published

This is the ninth in a series of posts about new features in the upcoming version 8.7 of Tcl. It describes more enhancements to Tcl's object-oriented programming facilities.

To take Tcl 8.7 for a spin, you can download a pre-alpha binary for your platform. Alternatively, you can build it yourself from the core-8-branch branch in the Tcl fossil repository.

NOTE: If you are not familiar with the object-oriented programming features in Tcl 8.6, see this tutorial.

Some of these enhancements were covered in a previous post:

  • Private variables and methods
  • Inline export / unexport modifiers
  • Additional slot operations

This post describes the following additional features:

  • Class variables and methods
  • Singleton and abstract classes

A future post will cover a final set of enhancements:

  • Convenience commands for common operations
  • Hooks for extending the class and object definition language
  • Utilities for creating custom definition dialects

Class variables

A class variable is a variable that is shared across all instances of a class. Its initial value can be set through a call to initialize in the class definition. Then within a method definition, the classvariable command is used to bring it into scope.

Here is an example to demonstrate usage. Suppose we only want a file to be opened at most once in our application. We can keep track of open files through a class variable.

oo::class create File {
  initialize {
    variable OpenFiles
    set OpenFiles [dict create]
  }
  constructor {path} {
    classvariable OpenFiles

    set path [file normalize $path]
    if {[dict exists $OpenFiles $path]} {
      error "File already open"
    }
    my variable myPath
    set myPath $path
    dict set OpenFiles $path 1
  }
  destructor {
    my variable myPath
    if {[info exists myPath]} {
      classvariable OpenFiles
      dict unset OpenFiles $myPath
    }
    return
  }
}

And a demonstration,

% File create fd1 foo.txt
::fd1
% File create fd2 bar.txt
::fd2
% File create fd3 foo.txt
File already open
% fd1 destroy
% File create fd3 foo.txt
::fd3

Internally, class variables are created within the class object itself (remember classes are also objects in the TclOO world). The classvariable command is then similar to commands like upvar in that it links that variable to a local variable of the same name in a method.

Similar functionality could be implemented in 8.6 as demonstrated by the ooutil package in Tcllib. Tcl 8.7 brings the capability into the core.

Class methods

Analogous to class variables are class methods which run within the context of the defining class as opposed to the defining class instance. Continuing the above example and extending the definition of our File class, we can define a class method.

% oo::define File {
  classmethod numfiles {} {
    my variable OpenFiles
    return [dict size $OpenFiles]
  }
}
% fd3 numfiles
2

Note above that numfiles uses my variable, not classvariable, to reference OpenFiles. This is because, as a class method, it executes within the context of File itself and not the fd3 instance.

Moreover, you do not even need an instance of the class to call a class method. You can directly invoke it on the class.

% File numfiles
2

Again, like class variables, similar functionality is available for Tcl 8.6 through the ooutil package in Tcllib.

Singletons

The singleton pattern is employed when you want at most one instance of a class to exist. Why? Ask google for use cases. In Tcl 8.7, you can implement this pattern simply by defining the class with oo::singleton instead of oo::class.

oo::singleton create TheOne {
  method id {} {return [self]}
}

All attempts to obtain a new TheOne object return the same object.

% set o [TheOne new]
::oo::Obj87
% set o2 [TheOne new]
::oo::Obj87

Moreover, you cannot create a named object of that class using create.

% TheOne create one
unknown method "create": must be destroy or new

Note that classes inheriting from a singleton class are not restricted.

% oo::class create ChildOfOne { superclass TheOne }
::ChildOfOne
% ChildOfOne new
::oo::Obj90
% ChildOfOne new
::oo::Obj91

Abstract classes

Another common pattern is the definition of abstract classes that are intended to be used as a base class and not be directly instantiated. In Tcl 8.7, the oo::abstract command can be used in lieu of oo::class to create abstract classes.

oo::abstract create RegularPolygon {
  constructor {sides len} {
    my variable Sides
    my variable Len
    set Sides $sides
    set Len $len
  }
  method perimeter {} {
    my variable Sides
    my variable Len
    return [expr {$Sides * $Len}] 
  }
}

An attempt to create an instance of RegularPolygon fails.

% RegularPolygon create poly
unknown method "create": must be destroy

However, we can derive a class from it.

oo::class create Square {
  superclass RegularPolygon
  constructor {len} {
    next 4 $len
  }
  method area {} {
    my variable Len
    return [expr {$Len * $Len}]
  }
}

We can then create objects from the derived class and call inherited methods on it as usual.

% Square create square 5
::square
% square area
25
% square perimeter
20

Unlike some languages like C++, abstract classes cannot be used to enforce interface requirements on child classes. So there is no way to say for example that every derived class must have an area method.

Coming up

We are almost done with TclOO, with one more post to go where I will describe some convenience features added to TclOO in 8.7 along with the ability define custom class definition dialects.

References

  1. TIP 478: Add Expected Class Level Behaviors to oo::class

  2. oo::abstract man page

  3. oo::classvariable man page

  4. oo::define man page

  5. oo::singleton man page