Consider the following:
$ cat reductio.tcl
oo::class create reductio {
initialise {
set m_foo garp
}
constructor {} {
classvariable m_foo
puts stdout $m_foo
}
}
set r [reductio new]
$ tclsh reductio.tcl
can't read "m_foo": no such variable
while executing
"puts stdout $m_foo"
(class "::reductio" constructor line 3)
invoked from within
"reductio new"
invoked from within
"set r [reductio new]"
(file "reductio.tcl" line 12)
Is this intended behaviour? It seems similar to the example on the oo::define manpage - https://www.tcl-lang.org/man/tcl9.0/TclCmd/ define.html#M39 - which does indeed work.
This has all the symptoms of me doing something obvious and wrong, but
as is also typical in this sort of case, I'm hanged if I can see it.
Please can someone help?
Alan
On 02/06/2026 21:54, Alan Grunwald wrote:
Consider the following:
$ cat reductio.tcl
oo::class create reductio {
initialise {
set m_foo garp
}
constructor {} {
classvariable m_foo
puts stdout $m_foo
}
}
set r [reductio new]
$ tclsh reductio.tcl
can't read "m_foo": no such variable
while executing
"puts stdout $m_foo"
(class "::reductio" constructor line 3)
invoked from within
"reductio new"
invoked from within
"set r [reductio new]"
(file "reductio.tcl" line 12)
Is this intended behaviour? It seems similar to the example on the
oo::define manpage - https://www.tcl-lang.org/man/tcl9.0/TclCmd/
define.html#M39 - which does indeed work.
This has all the symptoms of me doing something obvious and wrong, but
as is also typical in this sort of case, I'm hanged if I can see it.
Please can someone help?
Alan
I have spotted the difference - in the example on the manpage, the initialise script uses "variable ..." rather than "set ...". When I
change it in the above, it starts to work.
My real code, which is failing similarly already uses "variable ..." and
I'm slowly adding bits into the test class above to see what makes it fail.
On 02/06/2026 22:17, Alan Grunwald wrote:
On 02/06/2026 21:54, Alan Grunwald wrote:
Consider the following:
$ cat reductio.tcl
oo::class create reductio {
initialise {
set m_foo garp
}
constructor {} {
classvariable m_foo
puts stdout $m_foo
}
}
set r [reductio new]
$ tclsh reductio.tcl
can't read "m_foo": no such variable
while executing
"puts stdout $m_foo"
(class "::reductio" constructor line 3)
invoked from within
"reductio new"
invoked from within
"set r [reductio new]"
(file "reductio.tcl" line 12)
Is this intended behaviour? It seems similar to the example on the oo::define manpage - https://www.tcl-lang.org/man/tcl9.0/TclCmd/ define.html#M39 - which does indeed work.
This has all the symptoms of me doing something obvious and wrong, but as is also typical in this sort of case, I'm hanged if I can see it. Please can someone help?
Alan
I have spotted the difference - in the example on the manpage, the initialise script uses "variable ..." rather than "set ...". When I change it in the above, it starts to work.
My real code, which is failing similarly already uses "variable ..." and I'm slowly adding bits into the test class above to see what makes it fail.
A rather chunkier example this time; sorry but needs must:
$ cat reductio2.tcl
oo::class create reductio2 {
variable m_data
initialise {
variable m_columns [dict create]
}
classmethod createType {type line} {
classvariable m_columns
dict set m_columns $type [split $line ","]
}
classmethod dump {{chn stdout}} {
classvariable m_columns
puts $chn [format {m_columns: %s} $m_columns]
}
constructor {type line} {
classvariable m_columns
puts stdout [format {m_columns: %s} $m_columns]
if {![dict exists $m_columns $type]} {
throw {aaaaaaargh} \
[format \
{in constructor: "%s" is absent from m_columns (%s)} \
$type \
$m_columns]
}
set data [split $line ","]
if {[llength $data] != [llength [dict get $m_columns $type]]} {
throw "nope!" \
[format {wrong number of fields (%d): expected %d} \
[llength $data] \
[llength [dict get $m_columns $type]]]
}
set m_data $data
}
method dump {} {
puts stdout [format {m_data: %s} $m_data]
}
method get {type col} {
classvariable m_columns
if {![dict exists $m_columns $type]} {
throw {aaaaaaargh} \
[format {in get: "%s" is absent from m_columns (%s)} \
$type \
$m_columns]
}
set columns [dict get $m_columns $type]
if {!$col ni $m_columns} {
throw {whopsie!} \
[format {"%s" is absent from m_columns for %s (%s)} \
$col $type [dict get $m_columns $type]]
}
return [lindex $m_data [lsearch $columns $type]]
}
}
reductio2 createType foo "garp,parp,snarp"
set r2 [reductio2 new foo "1,2,3"]
$ tclsh reductio2.tcl
m_columns:
in constructor: "foo" is absent from m_columns ()
while executing
"throw {aaaaaaargh} [format {in constructor: "%s" is absent from m_columns (%s)} $type ..."
(class "::reductio2" constructor line 6)
invoked from within
"reductio2 new foo "1,2,3""
invoked from within
"set r2 [reductio2 new foo "1,2,3"]"
(file "reductio2.tcl" line 64)
The class is supposed to model a line of data in a CSV file. The client is supposed to pass the header line with column headings to createType, then create an object for each line of data and call get to get column values.
It seems that, in get, classvariable isn't working as I expect.
set vars [info vars ${ns}::*]::oo::Obj106: ::oo::Obj106::m_columns
if {$vars ne {}} {
puts "$ns: $vars"
}
}
On 6/2/2026 9:08 PM, et99 wrote:
Forgot to add the debugging info:
() 17 % foreach ns [namespace children ::oo] {
set vars [info vars ${ns}::*]::oo::Obj106: ::oo::Obj106::m_columns
if {$vars ne {}} {
puts "$ns: $vars"
}
}
::oo::Obj107: ::oo::Obj107::m_columns
() 18 % set ::oo::Obj106::m_columns
() 19 % set ::oo::Obj107::m_columns
foo {garp parp snarp}
() 20 % info object namespace ::oo::Obj107
::oo::Obj107 does not refer to an object
() 21 % info object class ::oo::Obj107
::oo::Obj107 does not refer to an object
() 22 % info na
d:/podcasts/Tcl902/bin/wish91.exe
() 23 % inf pa
invalid command name "inf"
() 24 % info pa
9.1a0
() 25 %
Consider the following:
$ cat reductio.tcl
oo::class create reductio {
initialise {
set m_foo garp
}
constructor {} {
classvariable m_foo
puts stdout $m_foo
}
}
set r [reductio new]
On 6/2/2026 4:54 PM, Alan Grunwald wrote:
Consider the following:
$ cat reductio.tcl
oo::class create reductio {
initialise {
set m_foo garp
}
constructor {} {
classvariable m_foo
puts stdout $m_foo
}
}
set r [reductio new]
Class variables are just links, per documentation:
"classvariable — create link from local variable to variable in class"
Not very readable, tbh, perhaps this is better :-)
classvariable makes an existing variable a "class" variable.
So if you modify your initialise as follows to properly create a
variable first, it works:
% oo::class create reductio {
initialise {
variable m_foo garp
}
constructor {} {
classvariable m_foo
puts stdout $m_foo
}
}
% reductio new
garp
::oo::Obj108
% reductio new
garp
::oo::Obj109
% reductio new
garp
::oo::Obj110
On 6/2/2026 4:54 PM, Alan Grunwald wrote:
I've never understood why
namespace eval foo {
set garp 42
}
behaves differently sometimes than
namespace eval foo {
variable garp 42
}
and sometimes behaves the same.
On 6/2/2026 4:54 PM, Alan Grunwald wrote:
Here is the link: https://www.magicsplat.com/tcl-docs/tcl/TclCmd/classvariable.html
Without context of what your code is trying to do, it is hard to provide feedback. I can take a look later when I have time.
initialize {::test1
variable m_columns [dict create initial {value1 value2 value3}]
}
classmethod setit {} {
classvariable m_columns
dict set m_columns foo {garp parp snarp}
puts [format {in setit: self=%s m_columns=%s} [self] $m_columns]
}
constructor {} {
classvariable m_columns
puts [format {in constructor: self=%s m_columns=%s} [self] $m_columns]
}
method showit {} {
classvariable m_columns
puts [format {in showit: self=%s m_columns=%s} [self] $m_columns]
}
}
On 6/3/2026 11:54 AM, saito wrote:
On 6/2/2026 4:54 PM, Alan Grunwald wrote:
::test1
% test1 setit
in setit: self=::test1 m_columns=foo {garp parp snarp}
% info object namespace ::test1
::oo::Obj106
On 6/3/2026 11:54 AM, saito wrote:
On 6/2/2026 4:54 PM, Alan Grunwald wrote:
info object namespace confirms that ::test1 is ::oo::Obj106.
So there are two completely independent m_columns variables:
::oo::Obj106::m_columns — visible to initialize, the constructor, and regular methods
::oo::Obj107::m_columns — visible only to classmethod
They never interact. self inside classmethod reports ::test1 which
is ::oo::Obj106, yet classvariable resolves to ::oo::Obj107.
Shouldn't there be just one m_columns, the class wide variable?
On 6/3/2026 7:08 PM, et99 wrote:
On 6/3/2026 11:54 AM, saito wrote:
On 6/2/2026 4:54 PM, Alan Grunwald wrote:
info object namespace confirms that ::test1 is ::oo::Obj106.
So there are two completely independent m_columns variables:
::oo::Obj106::m_columns — visible to initialize, the constructor, and regular methods
::oo::Obj107::m_columns — visible only to classmethod
They never interact. self inside classmethod reports ::test1 which is ::oo::Obj106, yet classvariable resolves to ::oo::Obj107.
Shouldn't there be just one m_columns, the class wide variable?
It seems to me that the use of classvariable inside the classmethod has forced the oo system to create a new copy of m_columns.
If you follow the change in my previous post, you will see that no m_columns variables are created for the objects. It exists once, and at class level.
% set r [test1 new]
in constructor: self=::oo::Obj109 m_columns=initial {value1 value2 value3} foo {garp parp snarp}
::oo::Obj109
% info vars ::oo::Obj109:*
% set r [test1 new]
in constructor: self=::oo::Obj110 m_columns=initial {value1 value2 value3} foo {garp parp snarp}
::oo::Obj110
% info vars ::oo::Obj110::*
However, the class copy still exists:
% info object namespace ::test1
::oo::Obj106
% info vars ::oo::Obj106::*
::oo::Obj106::m_columns
% set ::oo::Obj106::m_columns
initial {value1 value2 value3} foo {garp parp snarp}
Hi Saito:
So it turns out the fix is simple — use variable instead of classvariable inside a classmethod. However the real issue is that using classvariable inside a classmethod silently creates a completely separate isolated variable instead of linking to the class namespace, with no error or warning. This is very hard to diagnose.
This mirrors the behavior of classvariable inside initialize, which at least throws an explicit error — "self may only be called from inside a method". That error is actually helpful. The silent failure inside classmethod is much more dangerous because nothing indicates anything is wrong.
It would seem that classvariable should either be made to work correctly inside a classmethod, or at minimum throw the same kind of explicit error that it throws inside initialize, rather than silently doing the wrong thing.
-e
On 6/2/2026 4:54 PM, Alan Grunwald wrote:
Here is the link: https://www.magicsplat.com/tcl-docs/tcl/TclCmd/classvariable.html
Without context of what your code is trying to do, it is hard to provide feedback. I can take a look later when I have time.
On 6/3/2026 6:44 PM, et99 wrote:
Hi Saito:
So it turns out the fix is simple — use variable instead of
classvariable inside a classmethod. However the real issue is that
using classvariable inside a classmethod silently creates a completely
separate isolated variable instead of linking to the class namespace,
with no error or warning. This is very hard to diagnose.
This mirrors the behavior of classvariable inside initialize, which at
least throws an explicit error — "self may only be called from inside
a method". That error is actually helpful. The silent failure inside
classmethod is much more dangerous because nothing indicates anything
is wrong.
It would seem that classvariable should either be made to work
correctly inside a classmethod, or at minimum throw the same kind of
explicit error that it throws inside initialize, rather than silently
doing the wrong thing.
-e
I have filed a ticket here: https://core.tcl-lang.org/tcl/ tktview/477e96c250ba11df6cd4e8f548a0d06da078b0ed
On 6/2/2026 4:54 PM, Alan Grunwald wrote:
Here is the link: https://www.magicsplat.com/tcl-docs/tcl/TclCmd/classvariable.html
Without context of what your code is trying to do, it is hard to provide feedback. I can take a look later when I have time.
Alan Grunwald <[email protected]> wrote:
I've never understood why
namespace eval foo {
set garp 42
}
behaves differently sometimes than
namespace eval foo {
variable garp 42
}
and sometimes behaves the same.
Your two examples behave the same. Both create a variable named "garp"
in the "foo" namespace (which might be ::foo, but as you can nest
namespace eval's, could be ::some::other::path::foo if your namespace
evals were nested inside other namespace evals).
Where they behave differently is inside a proc. Plain set, with a
variable name without namespace qualifiers, simply creates a local
variable in the proc with that name. Nothing more, nothing less.
set, inside namespace eval foo, also just "creates a 'local' variable",
but this time, "local" to namespace foo, so you get foo::bar as a
variable.
The one that behaves differently is 'variable'. Inside a namespace
eval, but outside of a proc, it works just the same as a set. Where it behaves differently is inside a proc.
In a proc, the 'variable' command both "creates a proc local variable"
*and* links that proc local variable to a namespace variable of the
same name in the current namespace within which the proc is executing.
So:
proc abc {} {
set y 2
}
Creates a local 'y' inside proc abc. It exists inside abc after the
set, and is garbage collected when abc exits.
But
proc abc {} {
variable y 2
}
does two things. It also creates a local 'y' variable in the proc.
But it links that local 'y' to a namespace variable y in abc's current namespace. If 'abc' were inside ::foo::bar::baz then "variable y 2"
inside abc creates y inside abc, and links that y to ::foo::bar::baz::y
such that changes to the value in "y" inside abc also appear as changes to ::foo::bar::baz::y as well.
But, note that there is also a 'variable' command in TclOO, which
behaves differently from the 'variable' command used in procs and
namespaces. That overlap is unfortunate, but the ship has also sailed
there quite some time ago as well.
On 6/3/2026 6:44 PM, et99 wrote:
It would seem that classvariable should either be made to work
correctly inside a classmethod, or at minimum throw the same kind of
explicit error that it throws inside initialize, rather than silently
doing the wrong thing.
I have filed a ticket here: https://core.tcl-lang.org/tcl/ tktview/477e96c250ba11df6cd4e8f548a0d06da078b0ed
On 04/06/2026 03:27, et99 wrote:
On 6/3/2026 6:44 PM, et99 wrote:Many thanks.
I ended up by working around the problem as I would have done before
seeing about class variables in the Tcl 9 manpages - i.e. creating a variable explicitly in a namespace with the same name as the class, explicitly creating a proc in that namespace and using it as I would a classmethod, and explicitly referencing the namespace'd variable in the class methods.
Inside a proc
variable foo garp
creates a variable in the same namespace as that in which the proc was created. The variable persists when the proc returns, ...
but the variable created by
set foo garp
is cleared away when the proc returns.
| Sysop: | DaiTengu |
|---|---|
| Location: | Appleton, WI |
| Users: | 1,123 |
| Nodes: | 10 (0 / 10) |
| Uptime: | 36:28:18 |
| Calls: | 14,371 |
| Files: | 186,380 |
| D/L today: |
2,470 files (702M bytes) |
| Messages: | 2,540,649 |