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,142 |
| Nodes: | 10 (0 / 10) |
| Uptime: | 93:19:32 |
| Calls: | 14,704 |
| Calls today: | 2 |
| Files: | 186,497 |
| D/L today: |
2,407 files (794M bytes) |
| Messages: | 2,551,666 |