The following descriptions assume that the reader is familiar with
Tcl and with Java, C++, or a similar OO programming language.
- Similarities with XOTcl, ITcl, and Java
-
In its syntax, Toe resembles ITcl;
in its feature set, Toe is closer to XOTcl;
with respect to design principles, Toe is modeled on Java.
Toe also adopts some syntax and semantics from Java and C++,
especially where no Tcl precedent exists. For example, the keywords,
new, delete, public,
protected, and private are all part of Java,
C++ and Itcl, but are not commands or keywords in Tcl. In the Toe package,
new and delete are subcommands to the command, toe.
(One can also use Tcl's interp alias {} ... construct to make
new and delete first-class commands, although doing so may be controversial.)
As in Itcl [1],
Toe uses the method and common keywords.
Within class definitions, Toe also supports the subcommands,
my,
self,
and next,
modeled on their namesakes in TclOO.
- Classes
-
The elementary components for Toe are classes, interfaces, and mixins.
Of these, the main component is the class. A class is a specification
for executable objects, which are created by the Toe runtime.
The class defines variables and methods.
For reference, a class variable corresponds to a Tcl namespace variable.
A class method corresponds to a Tcl proc, and
extends a Tcl proc by implicitly declaring to be within local scope
all class variables, and some inherited variables.
A method may also be defined such that class and inherited variables are not declared.
For a given method, the method keyword may be followed by the
-novars option to prevent variables being included automatically.
A class may be instantiated multiple times to create separate objects.
Each object/instantiation is a namespace that contains the same methods,
but holds data values that are unique to that object.
The class may also be revised or redefined,
but otherwise exists for the life of the application.
- Inner (nested) classes
-
A class may define another class to be a member within itself.
Access to the inner class from outside its containing class is possible,
but not facilitated by Toe.
The purpose of this design feature is to allow complex behavior within one
class to be packaged internally as a separate class, and
to provide an additional measure of isolation. As constructed,
methods of the inner class have no direct access to members of the outer class.
- Access control: "public" "protected" and "private"
-
Analogous to their use in Java and C++, the keywords,
public:, protected:, and
private:, and their non-modal equivalents,
public, protected, and
private,
apply to variables and methods in class definitions.
Public methods may be accessed by name from outside of the object by qualifying
the object command with the method name.
They may be accessed from within methods of the object either directly
or by qualifying the subcommand, my.
Public and protected methods of a base class may also be
accessed directly from within a derived class.
Private methods may only be accessed directly from within methods of the class.
All methods in an object may be accessed directly from within any other method
of the same object.
The default access is public.
Public and protected methods and variables are inherited, although differentially.
For more on the interaction of class inheritance and access to methods and variables,
see below, under Inheritance.
- Objects
-
An object of a class corresponds to a Tcl namespace and at least one namespace ensemble.
Each object is created from an internal representation of the class.
Dynamic objects are initialized by a "constructor" method,
which is invoked internally as part of object creation.
Objects may be created and deleted throughout the life of the application.
The mapping between Tcl namespaces and the common and dynamic objects
is managed by the Toe runtime. In general, the programmer does not need to know
which namespace is associated with an object.
The common object of the inner class may be invoked from a method in its enclosing class
by a command consisting of "my" plus its class name.
Instantiations of an inner class may be created with toe new,
and invoked within an instance of the enclosing class.
- Dynamic objects
-
A dynamic object is one instance of a class. It is created by using the
toe new classname command.
If the constructor for the class requires input variables, they can be provided as
extra arguments to new.
All instance methods of the object are available at the time that the constructor is called.
If the class to be instantiated inherits from a base class, then an instance of the
base class is created before the derived class is instantiated,
and its constructor is called before the derived class' constructor is called.
Common variables and methods for the class are also available at instantiation time.
- Common objects
-
A single, common object is created for each class at the completion of parsing
a class specification. The common object is populated by variables and methods
that are defined with a leading common keyword, or while
the common scope applies, as specified by the common: keyword.
The conjugate keyword object: can be
used to revert to the default scope for subsequent class member specifications.
The access specifiers,
public and private, can be used with members of
the common object. The protected keyword can also be used for common
members, but access is converted internally to public,
since class inheritance does not apply to the common object.
Private methods in the common object are accessible only from other methods
in the common object.
The command name for the common object of a class is the class name,
either preceded by "::toe" or in the global namespace, depending on the current
policy setting for command naming.
The command name for the common object of an inner class is the fully qualified
name of the inner class, which includes its outer class name(s), joined with the
namespace separator, "::".
For example, using the global namespace:
% toe class C {
common:
method hello {} {puts "Hello, world"}
}
# C
% C hello
# Hello, world
For reference, a class of only common members produces the analog of a Tcl
namespace being used as an object.
The above example corresponds to the following pure Tcl code:
namespace eval ::C {
proc hello {} {puts "Hello, world"}
namespace ensemble create -subcommands hello
}
The following example, with namespace scoping enabled,
demonstrates access to a common public variable:
toe class C {
common public variable tag "some tag value"
}
# C
% toe::C variable tag
some tag value
- A Tcl command for each object
-
Objects are created with the toe new command, and deleted by the
toe delete command.
The return value of toe new is the object's command name.
Object command names are assigned by Toe, and cannot be changed.
A specific command name may appear to be intelligible,
but should be treated as an opaque token.
Command names for deleted objects are not reused in the same Toe session.
With the scoped naming policy enabled, a "Hello, world" message might be
implemented in a dynamic object and invoked as follows:
toe class Hello {
public method hello {} { puts "Hello, world" }
}
# Hello
% set obj [toe new Hello]
# ::toe::Hello#1
% $obj hello
# Hello, world
% toe delete $obj
- Direct access to common variables
-
In general, variables in a common object are managed by methods in the common object.
Additionally, common variables may be accessed for both reading and writing
from within a method of a dynamic object of the same class. Direct, read-write
access is provided by the my common list command, which brings
the listed variable(s) into the method's scope.
Extending the above example, if we want a method in an instance of class C
to change the value of the variable, tag, in the common object for class C,
we might add a method like the following to class C:
toe class C {
common private variable tag
public method settag {s} {
my common tag
set tag "$s"
return
}
}
- Interfaces
-
Interfaces are elementary components that specify
public or protected methods, and public constants.
An interface is specified by the command, toe interface.
An interface, like a class, is an abstract specification.
It does not provide an implementation for the methods that it specifies.
It provides a way to express and enforce method names, method arguments, and access.
Any class that implements an interface should implement all the methods that the
interface specifies. Methods specified but not implemented will produce
an error when called.
In an interface, a method is specified by the access, its name and argument list.
Interfaces may be used with any class -- they have no binding
to a particular class. A class references an interface for implementation
by naming the interface as a list value to the class option,
implements:
toe interface I {...}
toe interface J {...}
toe class C implements I {...}
toe class D implements {I J} {...}
A class may also reference an interface for abstraction.
When a class abstracts an interface, the named interface must be implemented
by another class that inherits the abstracting class.
Attempting to call the interface methods of an abstracting class
without proper inheritance will generate an error.
Abstraction is specified by naming the interface as a list value to the class option,
abstracts:
toe interface I {...}
toe class C abstracts I {...}
An interface provides:
a) a modular abstraction of zero or more methods,
b) a requirement for method implementations in a class that implements it,
c) a convenient container in which to place API-level comments, and
d) a design element to help with establishing class purpose and relationships.
Interfaces may specify constant values with the const keyword.
This keyword is only meaningful within the definition of an interface.
A constant defined in an interface can be referenced by name from dynamic object methods
of a class that implements the interface, using a qualifier to the subcommand,
my.
See below, under Multiple interfaces.
- Interfaces as class "API's"
-
An interface can be used to encapsulate the application programmer's interface for a class.
Additionally, in elaborate programming environments, such as team programming or
vendor-customer solutions development, a fully documented interface may be exposed to external
clients, while the implementation need not be disclosed at the same time.
Consider the following example for a simple class to manage a stack of arbitrary items:
toe interface IStack {
method push {item}
method pop {}
method peek {}
method size {}
}
toe class Stack implements IStack {
private variable stack
method constructor {args} {set stack [list]}
public:
method push {item} {set stack [linsert $stack 0 "$item"];return}
method pop {} {set stack [lassign $stack item];return $item}
method peek {} {return [lindex $stack 0]}
method size {} {return [llength $stack]}
}
Functionally, the interface in this example would seem to add nothing
to runtime behavior. Under the covers, the Toe package uses it to perform
error-checking against an implementing class.
If an inconsistency is detected, then a "class-compile-time" error may be reported.
- Interfaces as weak "types"
-
The OO classes and interfaces that are described here should not be construed
as user-defined types, as they are generally understood in programming languages.
However, introspection may be used to obtain the names of interfaces that a class implements.
From within an object method, the qualified subcommand, self interfaces,
returns a list of interface names for the interfaces that the class implements.
From outside the object, the object's
command may be qualified by self interfaces to obtain the same list.
Application code can use the list to validate whether a specific object implements
a particular interface.
Interface name-checking is also supported implicitly by the Toe runtime,
as part of the Inversion of Control mechanism. (see below).
- Class inheritance
-
A class can inherit both methods and variables from another class.
Inherited methods, when invoked, execute in the scope of the instance
in which they were defined. If a method in a derived class has the same
name as a method in a base class then the derived method overrides the base method.
For variables, however, an error is reported at class definition time if a variable
in a derived class would replace a variable of the same name in the base class.
Control of exactly which variables and methods of a base class are inherited
is provided by the access specifiers, public,
protected, and private,
which are described above. Methods and variables which are declared as
public or protected in a base class are directly accessible in a derived class;
methods, but not variables, are also accessible in cascaded derived classes.
A method in a derived class with the same name as a method in its base class,
but with a different access specifier, causes the derived method to override the
corresponding base class method, and to be treated with the new access.
The overriding method should define the same argument list, but differences in
the argument list are not enforced as part of the inheritance mechanism.
Multiple definitions of a method by the same name are allowed,
but only the last encountered method definition is used.
Public and protected variables of a base class are inherited in a derived class,
and declared in all methods of the derived class.
However, unlike methods, variables are only inherited by the immediate derived class.
Variables that are declared as public or protected in ancestors of a base class
are not inherited in the derived class. This is an intentional limitation,
and is asserted as a feature.
- Single class inheritance
-
A class may inherit only one other class. Multiple inheritance is not supported.
A class may incorporate constants from one or more interfaces,
as well as methods from one or more mixins, but such inclusions are not considered
to be inheritance.
- Multiple interfaces
-
The value associated with the implements option in a class
definition is a non-null Tcl list.
If multiple interfaces define the same method name, and have the same argument count,
the effect is benign and of no consequence. If same-named methods in different
interfaces have different argument counts,
an error will result, because the corresponding class method
cannot satisfy both method declarations. The argument lists are allowed to differ
in one case: when the argument list for a method in the interface definition
consists of the Tcl keyword, args.
If multiple interfaces define a constant by the same name,
the constants are distinguished by the interface name.
Constants are referenced as subcommands to my,
where the subcommand is the fully qualified constant name, using array notation,
as in the following example:
toe interface I {const p 1.23} ; toe interface J {const q 4.56}
# J
toe class C implements {I J} {
method m {} {
puts "p = [my I(p)]"
puts "q = [my J(q)]"
}
}
# C
% set obj [toe new C]
# ::toe::C#1
% $obj m
p = 1.23
q = 4.56
- Inversion of Control
-
An important idiom for object-oriented frameworks is inversion of control.
Firstly, a framework is defined as a collaborating set of classes,
which collectively provide a resource or service to clients of the framework.
In a complete and consistent framework, clients call into a framework at
designated entry points, and the framework is fully implemented to respond accordingly.
Such a framework might also be called a toolkit.
In an incomplete framework, implementation is partial, but provision is made
for a client class to couple and collaborate with the framework, in order to
enable a framework's mission.
In the inversion of control idiom, a user-supplied collaborating class provides
completion to an otherwise incomplete framework.
In order to complete the framework, the collaborating client class must
implement a specific interface that the framework requires.
When a framework is completed by a collaborating and
conforming client class, a framework class can safely send messages
to the client class in order to fulfill the framework's functionality.
The mechanism in this package to support inversion of control has three requirements:
- an interface to be implemented by a framework-collaborating client class;
- an abstract framework class that references the interface in
its definition by the abstracts option;
- a client class that both implements the same interface
and inherits from the abstracting framework class.
It is recommended, but not necessary, to have a second interface, J, which
defines the framework's public entry points, and which the abstracting
framework class implements.
The methods declared by interface J may then be called on an object of the
derived class D, even though class D does not implement any of the methods
specified by interface J.
- Mixins
-
A "mixin" in this package is a specification, but not a class.
It is a component that specifies methods and
filters to be incorporated into class objects.
A mixin is specified by the toe subcommand, mixin, and a class references
a mixin as a list value to the class option, mixes, in the class
definition line. A class may reference multiple mixins in a Tcl list.
Mixin methods, like class methods, can use introspection, as well as
directly reference the class variables for the mixing class.
Mixin filters specify the name of a class-implemented method that is
to be bracketed with prepended and appended scripts.
A prepended script can affect the return value of the method.
But an appended script cannot affect the return value of the method.
In the definition of a filter, the special name, "*",
causes the filter to be applied to all methods of the mixing class.
If the mixing class is a base class, then the public mixed-in methods are inherited.
If a class and a mixin name the same method, then the mixed-in method applies.
If multiple mixins each contain a filter with the same name,
and a class having a method of the same name mixes them all in,
then the prepending and appending scripts are applied cumulatively,
with the semantics of successive enclosure,
where the filter from the first-listed mixin occurs inner-most.
Mixins are free-standing specification, and have no binding to a particular class.
However, variable names that appear in the code for a filter
may inadvertently access class variables in the mixing class,
introducing unexpected, class-specific dependencies.
Mixins provide a mechanism to adjust an existing implementation without disrupting
an otherwise stable design. Mixins also allow the inclusion of functionality
that is complementary or orthogonal to a core design and implementation,
such as for pre- and post-condition checking,
satisfying non-functional requirements, implementing aspects, logging, or persistence.
- Member name overriding
-
A method in a derived class with the same name as a method in a base class
replaces the base class version in the derived class. However, during execution,
if a method of the base class calls another method in the base class which
the derived class overrides, then the base class version of the method will
still be called. That is, method overriding applies in the context of a derived
class, or clients of the derived class, but not in the context of the base class
that incurs a method override. This is called the "local precedence" rule.
If an unambiguous "call-down" from base class to derived class is desired,
the preferred mechanism is to abstract an interface that specifies the method.
- Class constructor and destructor
-
Each class has a constructor and destructor method. If these methods are not
included explicitly as part of the class definition, then null methods are
provided implicitly. The private methods are called by the Toe runtime, as part of
creating, via new, or destroying, via
delete, an instance of the class.
Constructors and destructors of base classes are also called as part of
instantiating or deleting, respectively, a class that inherits.
In an inheritance hierarchy, the constructor of the most derived class is
invoked last. An explicit constructor should always be defined with
args as its argument list.
As an added feature, the constructor is also called if
the object is reset, copied, or cloned, using the Toe runtime services (see below).
The constructor and destructor methods are private, and
should not be called by application code.
It is good programming practice to limit the use of the constructor and destructor
to initializing and cleaning up class variables. In particular, a class variable that
is to be used as a Tcl array variable should be initialized in the constructor.
- Object ownership
-
An object created with the new command may be owned
by another object.
A dynamic object may be owned by either another dynamic object or a common object.
A common object may not be owned. Ownership is managed by the Toe runtime.
- Original owner
-
The original owner of a newly created instance is the object within which the
toe new executes. If new
is executed in the global namespace, then the object is originally not owned.
Deleting an object using toe delete from anywhere in the
application's code also removes the runtime record of ownership.
Attempting to delete an object by a command
name that is no longer valid produces an error.
- Dynamic ownership transfer
-
The owner of an object may be changed by the Toe runtime
subcommands, orphan, adopt and seize.
orphan places an object in the unowned state;
adopt changes the ownerhip status of an object from either being unowned
or owned by some object to being owned by the adopting object;
seize, if enabled, forces an object to become owned by the seizing object.
Seizing ownership is a normally disabled feature;
to enable this feature, enter: toe policy seize 1.
- Garbage Prevention
-
In compiled languages, "garbage" refers to a portion of an application's address space
for which references to the contents of that portion have
been removed or lost to the application.
Tcl is implemented such that garbage is not generated.
However, in the context of this package, it is possible for all known
references to an object to be lost or deleted, rendering
the object isolated by reference from within the Toe framework.
Such an object is essentially "garbage."
To prevent objects becoming unusable, object ownership and object lifetime
are coupled.
If an object that owns other objects is deleted,
then the objects owned by the object being deleted are automatically deleted
as part of the process of deleting the requested object.
This is to prevent an object from persisting when only its owner knows about it.
However, as with other command-creating object systems, the inverse situation
remains possible. The name of an object's command may be held
in a variable after the object that it references is deleted,
making the name stale as a command. Because of possible object volatility,
the user is encouraged to use the Tcl command,
info command name, to
verify as needed whether an object command name is still valid.
Owned objects may also be deleted programmatically before the requested object
is deleted, thus avoiding the need for runtime garbage prevention.
- Method-local objects deleted upon method completion
-
A feature of the new subcommand is that it recognizes the
-local option. When used in the body of a method, this option
causes the object created with toe new -local
to be deleted automatically when program control exits the method.
- Introspection
-
This package provides several features that supplement what is essential for
OO programming, primarily to return the state of classes and objects as the
software is running. The Toe runtime also supports introspection, through the
toe command and subcommands, where
the desired information is not specific to a class or an object.
- Introspection commands: "my" "self" and "next"
-
Introspection and self-aware commands in the TclOO package, namely,
my [2],
self [3], and
next [4], are
implemented in this package to be consistent with, and a superset of, the TclOO offerings.
(see Synopses and Examples)
- Additional runtime commands
-
For introspection exceeding a single class or object, the Toe runtime supports
toe info. To change the composition of a class, object, interface or mixin,
the runtime supports toe revise.
To set or review runtime policies, the runtime supports toe policy.
These commands are described in detail under Synopses and elsewhere.
- Object recycling and alternate constructors
-
A special feature of this package is a mechanism for a dynamic object to reset
itself, which returns the object to its state after its constructor method is invoked.
When the reset feature of the Toe runtime is invoked on an object,
all internally owned objects are deleted,
all class variables in the object are unset and reinitialized,
all objects in the inheritance hierarchy of the object are similarly reset,
and the constructor method for each object in the inheritance hierarchy is invoked.
This offers several benefits over deleting a current object and
replacing it with a new object of the same class:
- It's faster; the overhead of new and delete are avoided;
- Existing references to the object's command name remain valid;
- Any history of revisions to a specific dynamic object is preserved.
The Toe runtime also supports copying and cloning ("deep copying") an object.
- To copy an object means to create a new instance of the class for the object,
and to copy the value of the class variables from the variables in the object
being copied. Base objects in an inheritance hierarchy are also copied.
Any objects owned by the object being copied, or owned by objects in its inheritance
hierarchy, may either remain owned by the original object or be adopted by
the copied instance.
- To clone an object means to create a new instance of an object, new
instances of all objects in its inheritance hierarchy, new instances of all
objects that the original object owns, and new instances of objects owned by
base objects in the original object's inheritance hierarchy.
In addition, object command names in any of the class variables are replaced
in the cloned objects so that they properly reference newly owned objects.
- Rootless native hierarchy
-
When a class is defined without the inherits option,
and is instantiated using new, the resulting
object inherits nothing. This is consistent with instantiation in C++,
but differs from the treatment of classes in Java,
which implicitly include a root class, named "Object."
Toe replaces what may appear to be lost by not having an implicit root object
with exposed runtime functionality and extended introspection.
- Dynamic revision
-
Methods in classes, objects, interfaces and mixins can be added or revised with the
toe revise command. This includes methods in inner classes and common objects.
The interfaces and mixins that a class references can also be revised, to apply to
subsequent instantiations of the the class. Class variables cannot be added, removed
or renamed by dynamic revision.
- Programmable policies
-
To review or set runtime policies, Toe supports the command,
toe policy name ?0|1?.
With this command, the state of a runtime policy may be inspected, enabled or disabled.
Policies include garbage prevention, permission to redefine
classes, interfaces and mixins,
and a "strict" mode, which invokes more rigorous validation when a class is defined.
Policies are described further under Synopses and Styles.
- Pure Tcl
-
This package is implemented in pure Tcl, requiring Tcl 8.5 or later.
It uses only Tcl commands that are available in a safe interpreter,
and should be platform-neutral.
|