I stumbled over the variable scope when inheriting a class with TclOO.
The member variable nCrumbs below is not visible to the inherited class without repeating the declaration.
Is there a way to avoid replicating all the variable declarations from the superclass?
(I read through all the OO documentation, specifically oo::define and oo::object, also the non-exported stuff, googled for it. There are so many concepts to get around various things, I got lost.
I am looking for something that keeps the inherited class as simple as possible. The superclass may have any fancy complicated code in it, though.)
Any help would be greatly appreciated, thanks.
oo::class create toaster {
variable nCrumbs; #declaration
constructor {} {
set nCrumbs 0; #definition
}
method toast {nSlices} {
if {$nCrumbs > 50} {
error "== FIRE! FIRE! =="
}
set nCrumbs [expr $nCrumbs+4*$nSlices]
}
method clean {} {
set nCrumbs 0
}
}
oo::class create smartToaster {
superclass toaster; #inherit
variable nCrumbs; #<======= have to declare again
method toast {nSlices} {
if {$nCrumbs > 40} {
my clean
}
next $nSlices; #call superclass method
}
}
set clsToaster [smartToaster new]
$clsToaster toast 2
The variable is physically located (if you can say that for code) in a private namespace for the object instance. By doing variable
in the declaration, you are directing the method bindings to merely make it available without further commands.
But yes, subclasses have to use variable
as well to see it by default, or to use one of the standard Tcl variable scope management commands, e.g., upvar
or namespace upvar
, or even the private variable
method.
oo::class create smartToaster {
superclass toaster
method toast {nSlices} {
my variable nCrumbs
if {$nCrumbs > 40} {
my clean
}
next $nSlices
}
}
oo::class create smartToaster {
superclass toaster
method toast {nSlices} {
namespace upvar [namespace current] nCrumbs nc
if {$nc > 40} {
my clean
}
next $nSlices
}
}
It didn't used to be that way, but it was found to be just too confusing otherwise; the variable declarations for a class only affect the methods of that class, not its subclasses.
[EDIT]: It is possible to make a parent's variables also visible in the child via appropriate metaclass magic:
oo::class create Class {
superclass oo::class
constructor args {
next {*}$args
set cs [info class superclasses [self]]
while {[llength $cs]} {
set cs [concat [lassign $cs c] [info class superclasses $c]]
oo::define [self] variable -append {*}[info class variables $c]
}
}
}
Demonstrating this:
% Class create foo {
variable x
}
::foo
% Class create bar {
superclass foo
variable y
}
::bar
% Class create boo {
superclass bar
variable z
}
::boo
% info class variables boo
z y x
While I don't in general recommend this as it makes a subclass much more fragile when a superclass evolves, and it doesn't track any changes to the superclass, it's easy to set up with a little scripting. You just delegate the smartness to your own metaclass and use that to construct all your operational classes (which are purely conventional TclOO classes from there on).
Donal, thanks for answering.
So I am assuming there is no mechanism available by default to make all the superclass variables available.
My current solution is to collect all variable names and then declare them with one single call.
Yet, I have to repeat this in every method. I would want to put the declareSuperclassVars outside the methods.
Is this possible somehow? Perhaps with another approach?
Thanks
oo::class create toaster {
variable nCrumbs; #declaration
variable toTest; #another one nowhere defined
constructor {} {
set nCrumbs 0; #definition
}
method toast {nSlices} {
if {$nCrumbs > 50} {
error "== FIRE! FIRE! =="
}
set nCrumbs [expr $nCrumbs+4*$nSlices]
}
method clean {} {
set nCrumbs 0
}
method declareSuperclassVars {} {
my variable lSuperclassVars
set lSuperclassVars [info vars]; #fill variable list
uplevel 1 {
my variable lSuperclassVars
eval "my variable $lSuperclassVars"
}
}
}
oo::class create smartToaster {
superclass toaster; #inherit
#declareSuperclassVars; #<======= would like to do it here
method toast {nSlices} {
my declareSuperclassVars; #declare all at once
if {$nCrumbs > 40} {
my clean
}
next $nSlices; #call superclass method
}
}
set clsToaster [smartToaster new]
$clsToaster toast 2