Introducing Tcl 8.7 Part 10: Still More TclOO

Published

This is the tenth in a series of posts about new features in the upcoming version 8.7 of Tcl. It is the final post related to enhancements to Tcl's object-oriented programming facilities.

To take Tcl 8.7 for a spin, you can download the source distribution. Binary distributions for Windows are available from magicsplat and BAWT.

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

The following feature enhancements were discussed in eighth and ninth posts in this series:

  • Private variables and methods
  • Inline export / unexport modifiers
  • Additional slot operations
  • Class variables and methods
  • Singleton and abstract classes

This post covers the remaining ones:

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

Extending the definition language

Class definitions are constructed from special commands that are available inside definition scripts passed to oo::class create, oo::define and their ilk. Examples of these special commands are method, forward etc. Tcl 8.7 provides a way to cleanly define metaclasses that accept an expanded set of commands in these definition scripts.

This is easiest explained with an example. Suppose we wanted our definition language to automatically create getter and setter methods that would allow clients of the class to retrieve values of specific variables. This is what a sample class definition might look like:

ClassWithProperties create Employee {
  properties name salary
  method raise {increment} {incr salary $increment}
}

Note we are using ClassWithProperties and not oo::class to define our class since it makes use of the properties definition extension. The created class is exactly as would be defined by oo::class in other respects. We would like the above definition to create variables name and salary with methods get_name, set_name etc. that retrieve or set the values of the corresponding variables.

So how would one enable the above capability?

We first define a namespace in which to contain our properties implementation. Note the setting of the namespace path to include ::oo::define. The reason will be clear in a moment. The properties method then executes the method definitions for the getters and setters. The uplevel is required so the method command is invoked in the context of the class definition script (the last argument to the create method).

namespace eval property_impl {
  namespace path ::oo::define
  proc properties {args} {
      uplevel 1 [list variable {*}$args]
      foreach arg $args {
         uplevel 1 [list method get_$arg {} "return \[set $arg\]"]
         uplevel 1 [list method set_$arg {val} "set $arg \$val"]
      }
  }
}

The second step is to define our metaclass which understands the use of properties in class definition scripts. This metaclass, which we have imaginatively named ClassWithProperties, inherits from oo::class because it serves the same purpose except that it understands the additional properties command in class definitions. The reason it understands this new definition command is because we used the new Tcl 8.7 definitionnamespace command to configure the namespace for the definition scripts. Because the property_impl namespace path was configured to include the oo::define namespace, the standard definition commands like method, export etc. will retain their usual behavior.

oo::class create ClassWithProperties {
  superclass oo::class
  definitionnamespace ::property_impl
}

We can now define classes whose definition scripts can include properties in addition to the standard class definition commands.

ClassWithProperties create Employee {
  properties name salary

  method raise {increment} {incr salary $increment}
}

And to try it out,

% Employee create joe
::joe
% joe set_name Joe
Joe
% joe set_salary 35000
35000
% joe raise 5000
40000
% joe get_salary
40000

Of course, other classes with properties can be defined as well.

ClassWithProperties create City {
  properties name population
}

Note as an aside that you could actually add such new definition commands into the oo::define namespace too (even in 8.6). However, in the author's opinion it is not advisable to effect such global modifications that impact all components and potentially conflict with future enhancements.

Method callbacks

Tcl programming is often event driven wherein callbacks are registered via various mechanisms like socket accept scripts, timers, control commands, Tk bindings and so on. In Tcl 8.6, registering callbacks to methods was not difficult. Here is an example of a countdown timer.

oo::class create Countdown86 {
  constructor {ticks} {
    after 100 [list [self] tick $ticks]
  }
  method tick {ticks} {
    puts "Tick.."
    if {[incr ticks -1]} {
      after 100 [list [self] tick $ticks]
    }
  }
}

Then assuming the event loop is running,

% Countdown86 new 3
::oo::Obj88
Tick..
Tick..
Tick..

Tcl 8.7 provides some syntactic sugar to make the callback to methods a little more obvious. The callback command can be used within a method to generate a script to invoke a method from any context. Equivalent to the above,

oo::class create Countdown87 {
  constructor {ticks} {
    after 100 [callback tick $ticks]
  }
  method tick {ticks} {
    puts "Tick.."
    if {[incr ticks -1]} {
      after 100 [callback tick $ticks]
    }
  }
}

Not a big deal, but makes the code a little bit clearer.

For compatibility with some existing packages, the name mymethod can be used in lieu of callback.

Self within oo::define

Extending the definition language used in oo::define scripts as discussed earlier sometimes requires knowing what class is being defined. There was no reliable means of obtaining this information in Tcl 8.6. Tcl 8.7 allows the use of self without any arguments from within a class definition context to retrieve the name of the class being defined.

% oo::class create C { puts "[self]  being defined" }
::C  being defined
::C

The only thing worse than a bad use case is no use case and that is what I have here. The motivating use cases described in TIP 524: Custom Definition Dialects for TclOO are already covered by other Tcl 8.7 OO enhancements where this feature is possibly used under the covers.

Calling methods defined on a class

The new myclass command, which can only be used from within an object instance context, invokes a method defined on the class of the object instance. Here is a macabre example

% oo::class create C { method genocide {} {myclass destroy}}
::C
% C create o
::o
% info object is class C
1
% info object is object o
1
% o genocide
% info object is class C
0
% info object is object o
0

Invoking genocide results effectively in a call to C destroy resulting in the class and its instances being destroyed. Similar could be done in Tcl 8.6 with self class.

Mapping procedures to methods

The final TclOO enhancement (at the time of writing) to be described is the use of the link command to map procedure names to methods so they can be invoked without qualifying with the object name.

oo::class create LinkDemo {
   constructor {} {
     link m
   }
   method m {} { puts "in m" }
   method n {} { m }
}
% LinkDemo create linkobj
::linkobj
% LinkDemo create linkdemo
::linkdemo
% linkdemo n
in m

As seen above, method n calls m without needing to qualify it with my. Maybe convenient but ... yawn.

More useful is the fact that you can (a) use a different name and (b) even define it at a global level so it can be called from outside the object method context.

For example, suppose you have a singleton (see earlier post) object that controls access.

oo::singleton create AccessController {
  constructor {} { link {::check_access CheckAccess} }
  method CheckAccess {user pw} {return 1;}
}

Now being a singleton, we cannot create the instance of this class with a specific name. We have to use new.

% AccessController new
::oo::Obj109

Other components do not need to know the name of the object because they can simply call the check_access global proc which has been mapped to the appropriate method of the object.

% check_access apn npa
1

Not earth shattering, but convenient.

That's all folks

That ends the sub-series of posts related to TclOO enhancements. Next installment will zip (hint) ahead with the next topic in our series on Tcl 8.7.

References

  1. TIP 524: Custom Definition Dialects for TclOO

  2. TIP 470: Reliable Access to OO Definition Context Object

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

  4. callback man page

  5. link man page