• Namespace context separation problem

    From Luc@21:1/5 to All on Wed Jan 24 17:47:57 2024
    Hi. Here is the code:

    ------------------------
    namespace eval AllWidgets {
    namespace eval RightClickMenu {
    set ::[namespace tail [namespace current]] [namespace current]
    puts "set ::[namespace tail [namespace current]] [namespace current]"
    }
    }
    namespace eval CFG {
    namespace eval WidgetConfig {}
    }

    namespace eval CFG::WidgetConfig {
    set RightClickMenu {
    -font "Freesans 16"
    -background "#FFFFFF"
    -foreground "#000000"
    }
    }

    puts $CFG::WidgetConfig::RightClickMenu
    ------------------------

    In the first namespace, AllWidgets, I create a child namespace named RightClickMenu. In that child namespace, I create a global variable
    named ::RightClickMenu.

    I could probably just ditch that because it's a leftover of the old
    design (I am rewriting and redesigning everything), but I really would
    like to understand what is going on here.

    Note that I put a 'puts' statement there to debug the effect of the
    previous line. That previous line breaks the last line in the sample
    code.

    set ::RightClickMenu ::AllWidgets::RightClickMenu
    can't read "CFG::WidgetConfig::RightClickMenu": no such variable
    while executing
    "puts $CFG::WidgetConfig::RightClickMenu"
    (file "namespacetests.tcl" line 19)
    Compilation failed.

    The problem goes away if I delete that line that creates the global
    variable.

    I was very sure I would get the value of the variable RightClickMenu
    that was created inside the WidgetConfig namespace which is a child of
    the CFG namespace. To my dismay, it clashes with a global namesake, a
    variable that exists in another scope.

    Why?


    --
    Luc


    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rich@21:1/5 to Luc on Wed Jan 24 22:36:12 2024
    Luc <[email protected]d> wrote:
    Hi. Here is the code:

    Thanks, this is helpful, very helpful.

    ------------------------
    namespace eval AllWidgets {
    namespace eval RightClickMenu {
    set ::[namespace tail [namespace current]] [namespace current]
    puts "set ::[namespace tail [namespace current]] [namespace current]"
    }
    }
    namespace eval CFG {
    namespace eval WidgetConfig {}
    }


    puts $CFG::WidgetConfig::RightClickMenu
    ------------------------

    In the first namespace, AllWidgets, I create a child namespace named RightClickMenu. In that child namespace, I create a global variable
    named ::RightClickMenu.

    I could probably just ditch that because it's a leftover of the old
    design (I am rewriting and redesigning everything), but I really would
    like to understand what is going on here.

    You are not paying full attention to the namespace name resolution
    rules (set out in the namespace manual page):

    NAME RESOLUTION
    In general, all Tcl commands that take variable and command names sup‐
    port qualified names. This means you can give qualified names to such
    commands as set, proc, rename, and interp alias. If you provide a
    fully-qualified name that starts with a ::, there is no question about
    what command, variable, or namespace you mean. However, if the name
    does not start with a :: (i.e., is relative), Tcl follows basic rules
    for looking it up:

    • Variable names are always resolved by looking first in the cur‐
    rent namespace, and then in the global namespace.


    Given your code, the "AllWidgets" does indeed set a global variable.
    And it is a fully quailfied global, so none of the name resolution
    rules come into play.

    But, here (for the code you posted):

    namespace eval CFG::WidgetConfig {
    set RightClickMenu {
    -font "Freesans 16"
    -background "#FFFFFF"
    -foreground "#000000"
    }
    }

    When this snippet is executed, CFG::WidgetConfig does not exist
    (because none of the posted code created it before), so it contains no
    variable names.

    You then try to 'set' an unqualified variable name within the namespace creation script. That invokes the name resolution rules. So what Tcl
    does is:

    1) looks in current namespace (i.e., CFG::WidgetConfig) for a variable
    named "RightClickMenu" and finds the current namespace has no variable
    in it of that name (remember, it is empty at this point). So it finds
    nothing, and invokes the second half of the "variable name" look rule.

    2) it now looks in the global namespace, and it finds "RightClickMenu"
    as a global variable in the global namespace. So Tcl updates the value
    stored in ::RightClickMenu for you.

    You can see this effect if you run your code, one statement at a time,
    from a REPL:

    $ rlwrap tclsh
    % namespace eval AllWidgets {
    namespace eval RightClickMenu {
    set ::[namespace tail [namespace current]] [namespace current]
    puts "set ::[namespace tail [namespace current]] [namespace current]"
    }
    }
    set ::RightClickMenu ::AllWidgets::RightClickMenu
    % info globals
    tcl_rcFileName tcl_version argv0 argv tcl_interactive RightClickMenu auto_path auto_index env tcl_pkgPath tcl_patchLevel argc tcl_library tcl_platform
    % set ::RightClickMenu
    ::AllWidgets::RightClickMenu
    % namespace eval CFG {
    namespace eval WidgetConfig {}
    }
    % namespace eval CFG::WidgetConfig {
    set RightClickMenu {
    -font "Freesans 16"
    -background "#FFFFFF"
    -foreground "#000000"
    }
    }

    -font "Freesans 16"
    -background "#FFFFFF"
    -foreground "#000000"

    % puts $CFG::WidgetConfig::RightClickMenu
    can't read "CFG::WidgetConfig::RightClickMenu": no such variable

    Note I added in an 'info globals' and set ::RightClickMenu just to show
    that the variable is now in the global namespace, and to show it's
    contents, before running the last two statements.

    Now, look what the contents of the global variable are, after the last
    two statements:

    % set ::RightClickMenu

    -font "Freesans 16"
    -background "#FFFFFF"
    -foreground "#000000"

    %

    Presumably you want the namespace'ed RightClickMenu to be in that
    namespace (implied by your puts of the fully qualified version). In
    which case you need to first create the variable in the namespace,
    which is the purpose of the "variable" command.

    Watch:

    $ rlwrap tclsh
    % namespace eval AllWidgets {
    namespace eval RightClickMenu {
    set ::[namespace tail [namespace current]] [namespace current]
    puts "set ::[namespace tail [namespace current]] [namespace current]"
    }
    }
    set ::RightClickMenu ::AllWidgets::RightClickMenu
    % set ::RightClickMenu
    ::AllWidgets::RightClickMenu
    % namespace eval CFG {
    namespace eval WidgetConfig {}
    }
    % namespace eval CFG::WidgetConfig {
    variable RightClickMenu
    set RightClickMenu {
    -font "Freesans 16"
    -background "#FFFFFF"
    -foreground "#000000"
    }
    }

    -font "Freesans 16"
    -background "#FFFFFF"
    -foreground "#000000"

    % puts $CFG::WidgetConfig::RightClickMenu

    -font "Freesans 16"
    -background "#FFFFFF"
    -foreground "#000000"

    % set ::RightClickMenu
    ::AllWidgets::RightClickMenu
    %

    You first have to make the variable 'exist' in the namespace, which is
    what the 'variable' command will do for you. Note that the global is
    no longer modified in this changed bit of code.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Luc@21:1/5 to Rich on Wed Jan 24 20:46:56 2024
    On Wed, 24 Jan 2024 22:36:12 -0000 (UTC), Rich wrote:

    You are not paying full attention to the namespace name resolution
    rules (set out in the namespace manual page):

    Thanks, but you are confusing me for the first time. I really don't
    understand this:


    namespace eval CFG::WidgetConfig {
    set RightClickMenu {
    -font "Freesans 16"
    -background "#FFFFFF"
    -foreground "#000000"
    }
    }

    When this snippet is executed, CFG::WidgetConfig does not exist
    (because none of the posted code created it before), so it contains no >variable names.

    Yes, it does. First I create CFG:

    namespace eval CFG {...

    and right inside it I immediately create WidgetConfig:

    namespace eval WidgetConfig {}

    Only after that, I do namespace eval CFG::WidgetConfig {...
    By the time I do that, yes, CFG::WidgetConfig does exist. It was
    created immediately before. So you lost me there.


    You then try to 'set' an unqualified variable name within the namespace >creation script. That invokes the name resolution rules. So what Tcl
    does is:

    1) looks in current namespace (i.e., CFG::WidgetConfig) for a variable
    named "RightClickMenu" and finds the current namespace has no variable
    in it of that name (remember, it is empty at this point). So it finds >nothing, and invokes the second half of the "variable name" look rule.

    Unqualified? It finds nothing? That requirement is news to me. I assume
    the 'set' command has enough authority to officialize the creation and qualification of the new variable in Tcl's internal notary office.
    Manual:

    "If value is specified, then set the value of varName to value, creating
    a new variable if one does not already exist, ..."


    2) it now looks in the global namespace, and it finds "RightClickMenu"
    as a global variable in the global namespace. So Tcl updates the value >stored in ::RightClickMenu for you.

    Your words make sense, but the language's behavior doesn't. I absolutely assumed that being inside a namespace would make everything belong to
    that namespace by default. If Tcl prefers a global variable to a "local" variable in a command that takes place inside a namespace, I feel
    betrayed. I don't feel so safe coding inside a namespace anymore.



    You first have to make the variable 'exist' in the namespace, which is
    what the 'variable' command will do for you. Note that the global is
    no longer modified in this changed bit of code.

    The rest of your explanation is very thoughtful, but I still find it
    hard to understand or maybe just hard to *accept* that I need to use
    'variable' because 'set' won't do the job in spite of their having such (nearly?) identical descriptions in the manual. I just think that the 'variable' command is unwarranted and weird.

    Thank you.

    --
    Luc


    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rich@21:1/5 to Luc on Thu Jan 25 01:19:07 2024
    Luc <[email protected]d> wrote:
    On Wed, 24 Jan 2024 22:36:12 -0000 (UTC), Rich wrote:

    You are not paying full attention to the namespace name resolution
    rules (set out in the namespace manual page):

    Thanks, but you are confusing me for the first time. I really don't understand this:


    namespace eval CFG::WidgetConfig {
    set RightClickMenu {
    -font "Freesans 16"
    -background "#FFFFFF"
    -foreground "#000000"
    }
    }

    When this snippet is executed, CFG::WidgetConfig does not exist
    (because none of the posted code created it before), so it contains no >>variable names.

    Yes, it does. First I create CFG:

    Fair enough, when I wrote that I overlooked that you'd created it in
    the prior statement. But it is also still empty even at that time.

    namespace eval CFG {...

    and right inside it I immediately create WidgetConfig:

    namespace eval WidgetConfig {}

    Only after that, I do namespace eval CFG::WidgetConfig {...
    By the time I do that, yes, CFG::WidgetConfig does exist. It was
    created immediately before. So you lost me there.


    You then try to 'set' an unqualified variable name within the namespace >>creation script. That invokes the name resolution rules. So what Tcl
    does is:

    1) looks in current namespace (i.e., CFG::WidgetConfig) for a variable >>named "RightClickMenu" and finds the current namespace has no variable
    in it of that name (remember, it is empty at this point). So it finds >>nothing, and invokes the second half of the "variable name" look rule.

    Unqualified?

    Unqualified means without any :: in the name

    Unqualified: varname

    Qualified: varname::somevar

    Fully qualified: ::varname::somevar

    It finds nothing?

    Just before the "set" begings to be executed, there are no variables in
    that namespace (because you had not yet created any there).

    As the "set" is in the process of being executed, it first has to
    decide what final end variable will be the target. Because the name in
    the set command has no :: anywhere, and no leading ::, it is
    'unqualifieed', which triggers the "name resolution rules". Since the namespace is still empty (the set has not created any variables, as it
    is in the process of being interpreted) the name resolution rules do
    not find an existing namespace variable already present. Which
    triggers the "look in the global namespace" rule, an it finds one
    there. So Tcl picks the gloabl as the intended target, and uses it.

    That requirement is news to me.

    Read the namespace manual page.

    I assume the 'set' command has enough authority to officialize the
    creation and qualification of the new variable in Tcl's internal
    notary office. Manual:

    Modified by the namespace "name resolution" rules. That's what's
    messing you up.

    "If value is specified, then set the value of varName to value, creating
    a new variable if one does not already exist, ..."

    None of the above senteence defines "where" the end result will reside.
    The namespace name resolution rules are the "decide where" (as in what namespace) the name will refer.

    2) it now looks in the global namespace, and it finds "RightClickMenu"
    as a global variable in the global namespace. So Tcl updates the value >>stored in ::RightClickMenu for you.

    Your words make sense, but the language's behavior doesn't. I
    absolutely assumed that being inside a namespace would make
    everything belong to that namespace by default.

    Unfortunately not, that's a quirk of namespaces. Just doing 'set'
    inside one does not create variables in that namespace.

    If Tcl prefers a global variable to a "local" variable in a command
    that takes place inside a namespace, I feel betrayed. I don't feel
    so safe coding inside a namespace anymore.

    It is a complete non-issue if you use "variable" to first create them
    (which also acts as documentation of their existance) and/or initialize
    them (variable can both create, and give a value to, the new variable
    name).

    You first have to make the variable 'exist' in the namespace, which is
    what the 'variable' command will do for you. Note that the global is
    no longer modified in this changed bit of code.

    The rest of your explanation is very thoughtful, but I still find it
    hard to understand or maybe just hard to *accept* that I need to use 'variable' because 'set' won't do the job in spite of their having such (nearly?) identical descriptions in the manual. I just think that the 'variable' command is unwarranted and weird.

    The part of the 'set' man page you overlooked is this one:

    If varName includes namespace qualifiers (in the array name if it
    refers to an array element), or if varName is unqualified (does not
    include the names of any containing namespaces) but no procedure is
    active, varName refers to a namespace variable resolved according
    to the rules described under NAME RESOLUTION in the namespace
    manual page.

    When you run 'set' inside a namespace eval, you have "no active
    procedure", and when the variable name is 'unqualified" (no ::
    anywhere), you get the name resolution rules defined for namespaces.
    Which produces this somewhat unexpected result until one connects the
    dots between the name resolution rules and set's behavior.

    The reason you don't notice the effect unless inside a namespace eval
    is because when you are otherwise outside of a procedure you are
    already in the global namespace, so no matter how the name is resolved,
    it ends up in the global namespace.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Luc@21:1/5 to Rich on Thu Jan 25 00:33:10 2024
    On Thu, 25 Jan 2024 01:19:07 -0000 (UTC), Rich wrote:

    It is a complete non-issue if you use "variable" to first create them
    (which also acts as documentation of their existance) and/or initialize
    them (variable can both create, and give a value to, the new variable
    name).

    This is all beginning to settle down in my head and I think I understand
    it now. I just don't like it. I don't like it. If I am coding inside a namespace, I think the context of the namespace has to prevail.

    It's rare for me to dislike something about Tcl but well, it happens.

    And I've been coding some more here and guess what, I'm using 'variable' instead of 'set' all over the place now. I don't trust 'set' anymore.
    At least in namespaces no, I don't, I will probably never use 'set'
    inside a namespace again.

    Thank you.


    --
    Luc


    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rich@21:1/5 to Luc on Thu Jan 25 04:29:23 2024
    Luc <[email protected]d> wrote:
    On Thu, 25 Jan 2024 01:19:07 -0000 (UTC), Rich wrote:

    It is a complete non-issue if you use "variable" to first create them >>(which also acts as documentation of their existance) and/or initialize >>them (variable can both create, and give a value to, the new variable >>name).

    This is all beginning to settle down in my head and I think I understand
    it now. I just don't like it. I don't like it. If I am coding inside a namespace, I think the context of the namespace has to prevail.

    It's rare for me to dislike something about Tcl but well, it happens.

    And I've been coding some more here and guess what, I'm using 'variable' instead of 'set' all over the place now. I don't trust 'set' anymore.
    At least in namespaces no, I don't, I will probably never use 'set'
    inside a namespace again.

    That's for the best, the name resolution rules make set, in a namespace
    but outside a proc, a bit of a footgun.


    Also note that 'variable' is useful for using namespace variables
    inside of procs as storage that outlives the proc's execution.

    I.e.:

    ---code---
    namespace eval ::example {
    variable yy 42

    proc printyy {} {
    variable yy
    puts "yy is '$yy'"
    }

    proc changeyy {value} {
    variable yy
    set yy $value
    }
    }

    puts [info globals]

    puts "First printyy"
    example::printyy

    puts "Calling changeyy"
    example::changeyy hello

    puts "Second printyy"
    example::printyy
    --end code---

    Running this results in:

    tcl_rcFileName tcl_version argv0 argv tcl_interactive auto_path env tcl_pkgPath tcl_patchLevel argc tcl_library tcl_platform
    First printyy
    yy is '42'
    Calling changeyy
    Second printyy
    yy is 'hello'

    Or, another useful use, a 'counter' that remembers its last value:

    $ rlwrap tclsh
    % namespace eval ::counter {
    variable count

    proc get {} {
    variable count
    return [incr count]
    }
    }
    % counter::get
    1
    % counter::get
    2
    % counter::get
    3
    % counter::get
    4
    %

    This last use (the counter) is for all intents, a 'self made object'.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From et99@21:1/5 to Rich on Thu Jan 25 13:02:47 2024
    On 1/24/2024 8:29 PM, Rich wrote:
    Luc <[email protected]d> wrote:
    On Thu, 25 Jan 2024 01:19:07 -0000 (UTC), Rich wrote:

    It is a complete non-issue if you use "variable" to first create them
    (which also acts as documentation of their existance) and/or initialize
    them (variable can both create, and give a value to, the new variable
    name).

    This is all beginning to settle down in my head and I think I understand
    it now. I just don't like it. I don't like it. If I am coding inside a
    namespace, I think the context of the namespace has to prevail.

    It's rare for me to dislike something about Tcl but well, it happens.

    And I've been coding some more here and guess what, I'm using 'variable'
    instead of 'set' all over the place now. I don't trust 'set' anymore.
    At least in namespaces no, I don't, I will probably never use 'set'
    inside a namespace again.

    That's for the best, the name resolution rules make set, in a namespace
    but outside a proc, a bit of a footgun.


    Also note that 'variable' is useful for using namespace variables
    inside of procs as storage that outlives the proc's execution.

    I.e.:

    ---code---
    namespace eval ::example {
    variable yy 42

    proc printyy {} {
    variable yy
    puts "yy is '$yy'"
    }

    proc changeyy {value} {
    variable yy
    set yy $value
    }
    }

    puts [info globals]

    puts "First printyy"
    example::printyy

    puts "Calling changeyy"
    example::changeyy hello

    puts "Second printyy"
    example::printyy
    --end code---

    Running this results in:

    tcl_rcFileName tcl_version argv0 argv tcl_interactive auto_path env tcl_pkgPath tcl_patchLevel argc tcl_library tcl_platform
    First printyy
    yy is '42'
    Calling changeyy
    Second printyy
    yy is 'hello'

    Or, another useful use, a 'counter' that remembers its last value:

    $ rlwrap tclsh
    % namespace eval ::counter {
    variable count

    proc get {} {
    variable count
    return [incr count]
    }
    }
    % counter::get
    1
    % counter::get
    2
    % counter::get
    3
    % counter::get
    4
    %

    This last use (the counter) is for all intents, a 'self made object'.


    Note that Luc's example in his first post does not error out in tcl 9.0b1. That is one of the incompatible changes that were made.

    Other code that needs to change is the use of non-fully qualified uses of global variables such as tcl_platform, which before tcl 9 would refer to the global variable, but not any longer, when inside a namespace eval.

    (bin) 1 % namespace eval foo {parray tcl_platform}
    "tcl_platform" isn't an array
    (bin) 2 % namespace eval foo {parray ::tcl_platform}
    ::tcl_platform(byteOrder) = littleEndian
    ::tcl_platform(engine) = Tcl
    ::tcl_platform(machine) = amd64
    ::tcl_platform(os) = Windows NT
    ::tcl_platform(osVersion) = 10.0
    ::tcl_platform(pathSeparator) = ;
    ::tcl_platform(platform) = windows
    ::tcl_platform(pointerSize) = 8
    ::tcl_platform(user) = core5
    ::tcl_platform(wordSize) = 4
    (bin) 3 % info pa
    9.0b1

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)