Author: Donal K. Fellows <[email protected]>
State: Final
Type: Project
Vote: Done
Created: 23-Oct-2018
Post-History:
Keywords: Tcl, object orientation, customization
Tcl-Version: 8.7
Tcl-Branch: tip-524
Vote-Summary: Accepted 2/0/4
Votes-For: DKF, JN
Votes-Against: none
Votes-Present: BG, KBK, FV, SL
Abstract
This TIP proposes a mechanism for allowing classes to control how their instances define themselves.
Rationale
Though TclOO has a powerful mechanism for handling definitions (essentially
based on executing Tcl scripts in a namespace that has the definition
commands, via a mechanism very similar to namespace eval
) it is not simple
for user code to extend those mechanisms in a way that is specific to a
particular set of classes. The oo::dialect package in Tcllib provides ways
of doing this, but is internally rather complex; I believe the process should
be much simpler.
Proposal
Every class will gain two internal fields that describe what namespace will be
used to configure the instances of that class (i.e., what namespace to use in
the namespace eval
). One of those fields will be used when the instance of
that class is being configured via oo::define
and the other will be used
when the instance is being configured va oo::define
(or self
within
oo::define
). When looking up what namespace to use, the inheritance
hierarchy will be used to find the first class in the resolution order that
defines a namespace; by default oo::object
will provide the namespace
::oo::objdefine
for one of the fields and oo::class
will provide
::oo::define
for the other, these being the current namespaces used for
definitions.
There will a new class definition for exerting control over these fields:
definitionnamespace ?kind? namespace
This allows the current class to have its definition namespace for kind set to namespace (or the empty string). The kind must be -class (the default) or -instance to set the definition namespace for oo::define and oo::objdefine respectively. The root classes, oo::object and oo::class, cannot have their definition namespaces modified.
There will be a new introspection command for reading what the current definition namespace is for a class:
info class definitionnamespace class ?kind?
This will return from class the name of the current definition namespace of kind kind (must be either -class or -instance; -class is the default) that is set for class. If class does not specify one, the empty string is returned.
Usage Example
Effective use of this feature depends on using namespace path
.
namespace eval mydialect {
namespace path ::oo::define
proc example {x} {
puts "This is an example($x) in [uplevel 1 self]"
method example {} [list puts example=$x]
}
proc constructor {arguments body} {
set body [string cat {puts "This is [self]";} $body]
tailcall ::oo::define constructor $arguments $body
}
}
puts "<<POINT-1>>"
oo::class create metaclass {
superclass oo::class
definitionnamespace mydialect
}
puts "<<POINT-2>>"
metaclass create cls {
constructor {abc} {
puts "abc = $abc"
}
example 123
}
puts "<<POINT-3>>"
set obj [cls create gorp xyz]
puts "<<POINT-4>>"
puts $obj
$obj example
Which will print:
<<POINT-1>>
<<POINT-2>>
This is an example(123) in ::metaclass
<<POINT-3>>
This is ::gorp
abc = xyz
<<POINT-4>>
::gorp
example=123
This second example (adapted from the proposed manual page) shows just how thoroughly the ability to control the definition language can change things.
namespace eval myDefinitions {
# Delegate to existing definitions where not overridden
namespace path ::oo::define
# A custom type of method
proc exprmethod {name arguments body} {
tailcall method $name $arguments [list expr $body]
}
# A custom way of building a constructor
proc parameters args {
uplevel 1 [list variable {*}$args]
set body [join [lmap a $args {
string map [list %VAR% $a] {
set [my varname %VAR%] [expr {double($%VAR%)}]
}
}] ";"]
tailcall constructor $args $body
}
}
# Bind the namespace into a (very simple) metaclass for use
oo::class create exprclass {
superclass oo::class
definitionnamespace myDefinitions
}
# Use the custom definitions
exprclass create quadratic {
parameters a b c
exprmethod evaluate {x} {
($a * $x**2) + ($b * $x) + $c
}
}
exprclass create cubic {
parameters a b c d
exprmethod evaluate {x} {
($a * $x**3) + ($b * $x**2) + ($c * $x) + $d
}
}
# Showing the resulting classes and objects in action
quadratic create quad 1 2 3
for {set x 0} {$x <= 4} {incr x} {
puts [format "quad(%d) = %.2f" $x [quad evaluate $x]]
}
cubic create cub 3 1 0.5 0.25
for {set x 0} {$x <= 4} {incr x} {
puts [format "cub(%d) = %.2f" $x [cub evaluate $x]]
}
This prints:
quad(0) = 3.00
quad(1) = 6.00
quad(2) = 11.00
quad(3) = 18.00
quad(4) = 27.00
cub(0) = 0.25
cub(1) = 4.75
cub(2) = 29.25
cub(3) = 91.75
cub(4) = 210.25
Implementation
See the branch tip-524
.
Copyright
This document has been placed in the public domain.