• classvariable in Tcl9.0.3

    From Alan Grunwald@[email protected] to comp.lang.tcl on Tue Jun 2 21:54:11 2026
    From Newsgroup: comp.lang.tcl

    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
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Alan Grunwald@[email protected] to comp.lang.tcl on Tue Jun 2 22:17:46 2026
    From Newsgroup: comp.lang.tcl

    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.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Alan Grunwald@[email protected] to comp.lang.tcl on Tue Jun 2 23:23:27 2026
    From Newsgroup: comp.lang.tcl

    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.


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From et99@[email protected] to comp.lang.tcl on Tue Jun 2 21:08:57 2026
    From Newsgroup: comp.lang.tcl

    Hi Alan:


    I tried to understand and debug your code using Claude. Here's what Claude (with my help) found.

    (Note, I don't fully understand this since it is complex tclOO beyond anything I've worked with, so take with a grain of salt.)


    **Changes made trying to get it working:**

    1. `initialise` (with an s) needed to be `initialize` (with a z) to be recognised as a valid TclOO keyword
    2. Added diagnostic `puts` of `[self]` and `[self class]` in the classmethods and constructor to track what context each was running in
    3. Replaced `classvariable m_columns` in the constructor and `get` with `upvar #0 [info object namespace [self class]]::m_columns m_columns` to try to explicitly reference the class namespace — but this didn't fix it either

    **What we discovered:**

    You are right that `classvariable` is not working as expected — and replacing it with an explicit `upvar` didn't help either. The problem is that `classvariable` in a `classmethod` resolves to a completely different namespace than `classvariable` anywhere else in the class.

    Specifically, when `createType` runs via `classmethod`, it stores `m_columns` in an auto-generated namespace `::oo::Obj107`. But `initialize`, the constructor, and `get` all use the class object's own namespace `::oo::Obj106`. These are two completely separate namespaces with no connection, and `::oo::Obj107` is not even a proper Tcl object — `info object namespace` and `info object class` both reject it.

    So `createType` successfully writes `foo {garp parp snarp}` to `::oo::Obj107::m_columns`, but the constructor and `get` look in `::oo::Obj106::m_columns` which is always empty — exactly the symptom you saw.

    This looks like a bug in `classmethod` in Tcl 9.1a0 — `classvariable` inside a `classmethod` does not resolve to the same namespace as the rest of the class. Since `classmethod` appears to be new in Tcl 9, it may not have been fully exercised yet. You may want to consider filing a bug report.

    Also noticed two bugs in `get` that are independent of the `classmethod` issue: 1. `if {!$col ni $m_columns}` should be `if {$col ni $columns}`
    2. `lsearch $columns $type` should be `lsearch $columns $col`


    -e




    On 6/2/2026 3:23 PM, Alan Grunwald wrote:
    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.



    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From et99@[email protected] to comp.lang.tcl on Tue Jun 2 21:16:00 2026
    From Newsgroup: comp.lang.tcl

    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}::*]
    if {$vars ne {}} {
    puts "$ns: $vars"
    }
    }
    ::oo::Obj106: ::oo::Obj106::m_columns
    ::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 %
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Alan Grunwald@[email protected] to comp.lang.tcl on Wed Jun 3 12:36:16 2026
    From Newsgroup: comp.lang.tcl

    On 03/06/2026 05:16, et99 wrote:
    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}::*]
        if {$vars ne {}} {
            puts "$ns: $vars"
        }
    }
    ::oo::Obj106: ::oo::Obj106::m_columns
    ::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 %

    Thanks for your input.

    At present, I have noted, and taken heart from, your suggestion that
    something isn't working as expected. I'll leave this for a few days, and
    if no-one comes forward to point out my error, I'll file a bug report.

    Just to bat away one of claude's assertions, "initialise" is explicitly documented as an alternative to "initialize". (I'm doing quite a bit of
    work with claude and I really like it/him, but I've learned to exercise caution regarding his/it's advice - most of it is excellent, but he/it's
    more than capable of dragging me down a rabbit hole of quite
    considerable depth. I also recall some discussion on here from a few
    months back from which I took that claude isn't really all that good at
    Tcl, possibly becuse the quality of a lot of Tcl code that's floating
    around on the interweb (from which claude may be expected to have
    learned) ) is of lower quality than what members of this group generally produce.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From saito@[email protected] to comp.lang.tcl on Wed Jun 3 09:10:49 2026
    From Newsgroup: comp.lang.tcl

    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


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Alan Grunwald@[email protected] to comp.lang.tcl on Wed Jun 3 18:58:14 2026
    From Newsgroup: comp.lang.tcl

    On 03/06/2026 14:10, saito wrote:
    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


    Thanks for your response.

    Where does you quotation about classvariables and links come from? I
    can't find the word "link" on the page describing oo::define for either
    Tcl9.0 (https://www.tcl-lang.org/man/tcl9.0/TclCmd/define.html) or
    Tcl9.1 (https://www.tcl-lang.org/man/tcl9.1/TclCmd/define.html). I
    couldn't actually find any documentation for classvariable anywhere.

    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. I guess the use in initialise scripts
    exhibits the same effect.

    I did, belatedly realise that my reductio example used "set" and the
    example on the manpage used "variable" and that when I changed it, the
    example started working correctly. About the same time, I discovered
    that my "real" code was using "variable" as well and wasn't working. I
    had intended to post some code that exhibitted the problem; I'm sorry if
    I forgot.

    Here is a heavier-weight example that uses "variable" and fails:

    $ 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)

    I don't understand what is going on.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From saito@[email protected] to comp.lang.tcl on Wed Jun 3 14:54:22 2026
    From Newsgroup: comp.lang.tcl

    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.


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Rich@[email protected] to comp.lang.tcl on Wed Jun 3 21:49:37 2026
    From Newsgroup: comp.lang.tcl

    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.

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From et99@[email protected] to comp.lang.tcl on Wed Jun 3 16:08:02 2026
    From Newsgroup: comp.lang.tcl

    On 6/3/2026 11:54 AM, saito wrote:
    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.





    Here's a small test case that seems to show the problem.

    % oo::class create test1 {
    initialize {
    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]
    }
    }
    ::test1

    % test1 setit
    in setit: self=::test1 m_columns=foo {garp parp snarp}
    % info object namespace ::test1
    ::oo::Obj106

    % set r [test1 new]
    in constructor: self=::oo::Obj108 m_columns=initial {value1 value2 value3} ::oo::Obj108
    % $r showit
    in showit: self=::oo::Obj108 m_columns=initial {value1 value2 value3}

    % info vars ::oo::Obj106::*
    ::oo::Obj106::m_columns
    % info vars ::oo::Obj107::*
    ::oo::Obj107::m_columns
    % info vars ::oo::Obj108::*

    % set ::oo::Obj106::m_columns
    initial {value1 value2 value3}
    % set ::oo::Obj107::m_columns
    foo {garp parp snarp}



    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?

    -e

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From saito@[email protected] to comp.lang.tcl on Wed Jun 3 20:43:29 2026
    From Newsgroup: comp.lang.tcl

    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:

    ::test1


    When the class is created, the initialise is called. So it assigns the
    initial value to class variable: {value1 value2 value3}


     % test1 setit
    in setit: self=::test1 m_columns=foo {garp parp snarp}
     % info object namespace ::test1
    ::oo::Obj106



    This use case is questionable. If you are already in a class method, all
    class variables are available to you, I'd think. So no need to declare
    or create new references or links via a "classvariable m_class" when a "variable m_class" is sufficient. I am not privy to the internal implementation details of the oo in Tcl9 so maybe a new copy gets
    created; who knows.

    Following the line above, If you replace "classvariable m_class" with a
    simple "variable m_class" things seem to work as you expect it.

    classmethod setit {} {
    variable m_columns
    ;## puts "SET : [string range [clock microseconds] end-4 end]"
    puts [format {in setit/1: self=%s m_columns=%s} [self] $m_columns]
    dict set m_columns foo {garp parp snarp}
    puts [format {in setit/2: self=%s m_columns=%s} [self] $m_columns]
    }


    % test1 setit
    in setit/1: self=::test1 m_columns=initial {value1 value2 value3}
    in setit/2: self=::test1 m_columns=initial {value1 value2 value3} foo
    {garp parp snarp}


    Just my 2cents. I am not familiar with Tcl 9's oo system so take
    everything with a spoonful of salt.


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From saito@[email protected] to comp.lang.tcl on Wed Jun 3 21:00:24 2026
    From Newsgroup: comp.lang.tcl

    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}











    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From et99@[email protected] to comp.lang.tcl on Wed Jun 3 18:44:16 2026
    From Newsgroup: comp.lang.tcl

    On 6/3/2026 6:00 PM, saito wrote:
    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

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From et99@[email protected] to comp.lang.tcl on Wed Jun 3 19:27:35 2026
    From Newsgroup: comp.lang.tcl

    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

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Alan Grunwald@[email protected] to comp.lang.tcl on Fri Jun 5 16:19:02 2026
    From Newsgroup: comp.lang.tcl

    On 03/06/2026 19:54, saito wrote:
    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.


    Many thanks. I think that after searching through numerous oo::...
    pages, I was too braindead to think to check for a classvariable page.

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Alan Grunwald@[email protected] to comp.lang.tcl on Fri Jun 5 16:28:07 2026
    From Newsgroup: comp.lang.tcl

    On 04/06/2026 03:27, et99 wrote:
    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

    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.

    I'll try to watch what's happening to the ticket.



    Alan
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Alan Grunwald@[email protected] to comp.lang.tcl on Fri Jun 5 16:32:46 2026
    From Newsgroup: comp.lang.tcl

    On 03/06/2026 19:54, saito wrote:
    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.


    I'm trying to write something to handle CSV data.

    I'd like to crate an object to model a line of data that will return a
    value, given a column name. I'd also like to allow multiple types of CSV
    data that have different columns.

    The idea is to register a type by passing a type name and list of column
    names to classmethod, then construct objects passing a type name and a
    list of values.

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Alan Grunwald@[email protected] to comp.lang.tcl on Fri Jun 5 16:38:26 2026
    From Newsgroup: comp.lang.tcl

    On 03/06/2026 22:49, Rich wrote:
    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.


    Thanks Rich,

    If I understand correctly...

    Outside a proc

    variable foo garp

    and

    set foo garp

    behave exactly the same.

    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.


    That is really useful and something I've never understood before.



    Alan
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From saito@[email protected] to comp.lang.tcl on Fri Jun 5 16:45:26 2026
    From Newsgroup: comp.lang.tcl

    On 6/3/2026 10:27 PM, et99 wrote:
    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.


    This sounds right. In any case, it may be working as designed and if so,
    it should be documented clearly.

    I have filed a ticket here: https://core.tcl-lang.org/tcl/ tktview/477e96c250ba11df6cd4e8f548a0d06da078b0ed


    Thanks.

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From saito@[email protected] to comp.lang.tcl on Fri Jun 5 16:45:30 2026
    From Newsgroup: comp.lang.tcl

    On 6/5/2026 11:28 AM, Alan Grunwald wrote:
    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.


    I am glad you got it working.

    There is a section on oo concepts in Ashok's book which was updated for
    Tcl 9 last year or so. I would recommend getting it. I haven't used/read
    it yet but here is what it says on this topic after a search now:

    "A class variable is a data member that is shared across all instances
    of a class. It is created and initialized within the script passed to
    the initialize command (Section 18.2.8) as part of a class definition.
    The classvariable command is then used to bring it into scope within a
    method context."

    And this is 18.2.8:

    "initialize INITSCRIPT

    The class initializer is a script that is called at the time of class definition to perform any additional setup of the namespace of the class object itself (Section 18.3.4). The initialize, alternately spelt as initialise, command runs this script within a class definition script.

    We saw an example of its use in Section 18.2.4.2.

    Do not confuse the constructor with the initializer. The former
    initializes objects (instances) of the class when they are created. The
    latter runs at class definition time in the namespace of the class
    object itself."



    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Rich@[email protected] to comp.lang.tcl on Fri Jun 5 22:25:15 2026
    From Newsgroup: comp.lang.tcl

    Alan Grunwald <[email protected]> wrote:
    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, ...

    Yes, with one tiny 'but' clause. If you use the two argument version
    of variable as you show above, inside of a proc, then you also reset
    the persistent namespace variable to "garp" each time you run the
    command (i.e. each time the proc executes).

    If this is what you want, that is fine. But if you want the old value
    stored in the namespace from the last time the variable was modified
    then inside the proc you just want to do: "variable foo" to "link" a
    local foo to the namespace ::name::foo (made up namespace ::name) so
    the proc can access whatever was last left behind there.

    The typical usage is for the two argument version:

    variable foo garp

    to be executed inside a namespace eval, but outside of any proc's, to
    create, and initialize with a value, the namespace variables
    (::name::foo).

    Then, the proc's defined in the namespace use "variable foo" to link to
    that ::name::foo variable, and use/modify it's value as they wish.

    Namespace variables act somewhat like "local globals". They are
    "global" to all the proc's in the namespace, but they are "local" to
    the namespace so they don't clash name wise with variables in other
    namespaces (assuming no namespace name clashes...).


    but the variable created by

    set foo garp

    is cleared away when the proc returns.

    Yes, assuming "set foo garp" was executed inside a proc. If you
    execute "set foo garp" inside a namespace eval, but outside of a proc,
    it creates a variable in the namespace much the same as "variable foo
    garp" does inside a namespace eval but outside of a proc.

    I.e.:

    $ rlwrap tclsh
    % info exists ::name::foo
    0
    % namespace eval ::name { set foo garp }
    garp
    % info exists ::name::foo
    1
    % set ::name::foo
    garp
    %

    vs:

    $ rlwrap tclsh
    % info exists ::name::foo
    0
    % namespace eval ::name { variable foo garp }
    % info exists ::name::foo
    1
    % set ::name::foo
    garp
    %
    --- Synchronet 3.22a-Linux NewsLink 1.2