Introducing Tcl 8.7 Part 10: Still More TclOO
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 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.
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.