• dicts vs. namespaces - who wins?

    From Luc@21:1/5 to All on Sun Jan 7 01:38:03 2024
    I am keeping tabs on my tabs (erm, sorry) with a dict:

    set ::TabControl [dict create]

    Then for example::

    set tabID [clock milliseconds]
    dict set ::TabControl $tabID "
    [list TCTabPosition $position]
    [list TCDirectory $dir]
    [list TCViewMode $::GUIoptions_DefaultLayout]
    "

    etc.

    Querying: set tabdir [dict get $tabID TCDirectory]


    I don't have any problem with it. It works.

    But it's occurred to me that maybe I could also do it all using
    namespaces:

    set tabID [clock milliseconds]
    namespace eval $tabID {
    set TCTabPosition $position
    set TCDirectory $dir
    set TCViewMode $::GUIoptions_DefaultLayout
    }

    Querying: set dir $tabID::TCDirectory


    Now I wonder if there would be any benefit in using namespaces instead
    of the dict. Shorter/less cluttered code maybe? Looks like it.

    What do you think? Any particular reason for one or another?



    Bonus questions if you are in the mood:

    1. Now that I think of it, what is the point of dicts anyway? What can
    you ever do with them that you can't do with namespaces? You know what
    you can do with a namespace that you can't do with a dict? Store an
    array in it:

    % array set aa {item1 a}
    % set dd [dict create]
    % dict set dd "array1 $aa"
    can't read "aa": variable is array
    % dict set dd "array1 [array get aa]"
    wrong # args: should be "dict set dictVarName key ?key ...? value"

    % namespace eval nn {}
    % array set nn::array1 [array get aa]
    % parray nn::array1
    nn::array1(item1) = a


    2. I vaguely remember reading somewhere that we can't trace a dict.
    Is that bad?


    3. Can we trace a namespace? I mean, name a namespace and have Tcl
    detect any changes that happen to variables and commands within that
    entire namespace?


    --
    Luc


    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rich@21:1/5 to Luc on Sun Jan 7 06:57:43 2024
    Luc <[email protected]d> wrote:
    I am keeping tabs on my tabs (erm, sorry) with a dict:

    set ::TabControl [dict create]

    Then for example::

    set tabID [clock milliseconds]
    dict set ::TabControl $tabID "
    [list TCTabPosition $position]
    [list TCDirectory $dir]
    [list TCViewMode $::GUIoptions_DefaultLayout]
    "

    Is there a reason why you don't use dict create above?

    dict set ::TabControl $tabID [dict create TCTabPosition $position TCDirectory $dir TCViewMode $::GUIoptions_DefaultLayout]

    Or if you want the 'alignment layout':

    dict set ::TabControl $tabID [dict create TCTabPosition $position \
    TCDirectory $dir \
    TCViewMode $::GUIoptions_DefaultLayout]


    Querying: set tabdir [dict get $tabID TCDirectory]


    I don't have any problem with it. It works.

    But it's occurred to me that maybe I could also do it all using
    namespaces:

    set tabID [clock milliseconds]
    namespace eval $tabID {
    set TCTabPosition $position
    set TCDirectory $dir
    set TCViewMode $::GUIoptions_DefaultLayout
    }

    Querying: set dir $tabID::TCDirectory

    You could, although this really isn't the /intended/ use of namespaces.
    Their intended use is for organizing sets of related procs and those
    procs associated variables, and for avoiding name conflicts with modules/packages.

    Now I wonder if there would be any benefit in using namespaces
    instead of the dict. Shorter/less cluttered code maybe? Looks like
    it.

    Unless you plan to also have specific procs for given tabs there's not
    likely any benefit, and in reality the dict access will likely be
    marginally faster.

    Plus, if you wanted to eventually save your state across exits and
    reload where you were, the dict method gives you that single data
    structure to save/restore. The plural namespaces means you need extra
    work to extract and serialize to disk the namespaces and later restore
    them all upon reload.

    Bonus questions if you are in the mood:

    1. Now that I think of it, what is the point of dicts anyway? What can
    you ever do with them that you can't do with namespaces? You know what
    you can do with a namespace that you can't do with a dict? Store an
    array in it:

    Bzzt, please try again.


    % array set aa {item1 a}
    % set dd [dict create]
    % dict set dd "array1 $aa"
    can't read "aa": variable is array
    % dict set dd "array1 [array get aa]"
    wrong # args: should be "dict set dictVarName key ?key ...? value"

    That is because you tried to do string interpolation to store the
    array. Don't do that. This works just fine:

    $ rlwrap tclsh
    % array set aa {item1 a}
    % set dd [dict create]
    % dict set dd array1 [array get aa]
    array1 {item1 a}
    % dict get $dd array1
    item1 a

    Granted, what is stored is the string representation of the array, but
    once stored, you can then 'dict get' against it:

    % dict get $dd array1 item1
    a

    2. I vaguely remember reading somewhere that we can't trace a dict.
    Is that bad?

    Traces work on dicts, what they don't let you target is traces on a
    single dict key, you have to write a bit of code to do that as part of
    a trace on the dict itself.

    In any case, if you are not using traces, the subtle distinction
    hardly matters.

    3. Can we trace a namespace? I mean, name a namespace and have Tcl
    detect any changes that happen to variables and commands within that
    entire namespace?

    No, not the namespace itself. You can apply traces to the variables
    and the procs in the namespace, but you can also apply traces to a
    variable holding a dict just as well.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Luc@21:1/5 to Rich on Sun Jan 7 13:35:04 2024
    On Sun, 7 Jan 2024 06:57:43 -0000 (UTC), Rich wrote:

    Is there a reason why you don't use dict create above?

    Yes, just code cleanliness. I declare all global variables at the
    beginning of the whole thing including four or five dicts that will be populated later on.


    Or if you want the 'alignment layout':

    I like alignment layout for debug printing, but I've found out it all
    goes away as soon as the dict is updated. I had to make a proc that "beautifies" the printing of the main dict. It wasn't very easy to
    write and it's only good for that one dict and its own unique structure.
    Doing the same with namespaces would be a lot easier and it would work
    with all dict-replacing namespaces.


    Plus, if you wanted to eventually save your state across exits and
    reload where you were, the dict method gives you that single data
    structure to save/restore. The plural namespaces means you need extra
    work to extract and serialize to disk the namespaces and later restore
    them all upon reload.

    That is a good point, but

    1. I feel that coding with dicts is by itself a lot of "extra work"
    because their structure is trickier. The command lines are longer and
    look a bit too fragmented to me, and inserting lists and sublists is a
    pretty good minefield so it's very easy to make a mistake and I have
    to be printing and checking them all the time. I feel very tempted to
    rewrite everything with namespaces now because it will be a lot easier
    to manage. There is still a lot to be done. I've barely done the basics
    because I am spending a lot of time coding around that text widget and
    image create limitation. That one problem alone is requiring a lot of management that is no fun at all. I had to introduce some stupid nesting
    to handle it and that too should be easier to manage with namespaces.

    2. Saving and restoring with namespaces is easy enough:
    foreach fe [info vars $::namespace::Config_*] {
    do something with $fe
    }


    Bzzt, please try again.

    That is because you tried to do string interpolation to store the
    array. Don't do that. This works just fine:

    $ rlwrap tclsh
    % array set aa {item1 a}
    % set dd [dict create]
    % dict set dd array1 [array get aa]
    array1 {item1 a}
    % dict get $dd array1
    item1 a

    Granted, what is stored is the string representation of the array, but
    once stored, you can then 'dict get' against it:

    % dict get $dd array1 item1
    a

    Thank you for the lesson, but I find that a little too convoluted. It
    seems to me that you in fact did not store an array in the dict but
    rather converted it into a list so it would be admitted by the dict's doorman/bouncer.


    In any case, if you are not using traces, the subtle distinction
    hardly matters.

    Not yet.


    Thank you.

    --
    Luc


    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rich@21:1/5 to Luc on Sun Jan 7 21:00:37 2024
    Luc <[email protected]d> wrote:
    On Sun, 7 Jan 2024 06:57:43 -0000 (UTC), Rich wrote:

    Is there a reason why you don't use dict create above?

    Yes, just code cleanliness. I declare all global variables at the
    beginning of the whole thing including four or five dicts that will
    be populated later on.

    But, if you are going for cleanliness, then using dict create is:

    1) cleaner

    2) more meaningful. The code declares exactly what it is doing. This
    can be esp. good if you sit this aside for a while and return in six or
    nine months. Seeing

    set x [dict create key1 value1 \
    key2 value2]

    clearly says a [dict] is being created.

    While

    set x "key1 value1
    key2 value2"

    Is just a string, that /could/ be a list, or /could/ be a dict, or
    /could/ eventually be turned into an array. Less overtly meaningful
    without first investigating how "x" is used later.

    3) safer.

    Doing "set x [dict create key1 $somevariable]" will always succeed.

    Doing "set x " key1 $somevariable" will /appear/ to also always
    succeed, until that fateful day, months or years in the future, where
    the string inside "$somevariable" happens to contain a character that
    is meaningful to Tcl's implicit list/dict conversion parser, at which
    point, the use of x as a dict will blow up with a very non-meaningful
    error message. And this will be made worse by the fact that you'll be
    left wondering: "it has been working for X time so far, what
    happened?".

    Watch:

    $ rlwrap tclsh
    % set somevar "a string"
    a string
    % set dict "key1 $somevar"
    key1 a string
    % dict get $dict key1
    missing value to go with key
    %
    vs:
    % set dict [dict create key1 $somevar]
    key1 {a string}
    % dict get $dict key1
    a string
    %

    Unless you have very good reason to be assembling dicts or lists by concatenating plain strings, and are "very careful" when doing so, it
    is best to *always* use the proper constructor procs:

    [dict create ...] for dicts
    [list ...] for lists

    Both take care of making sure the result is proper as a dict/list and
    for handing those special edge cases that are easily forgotten when
    doing "$a $b $c $d $e" to assemble what will be used as a dict/list.





    Or if you want the 'alignment layout':

    I like alignment layout for debug printing, but I've found out it all
    goes away as soon as the dict is updated.

    Because as soon as you update the dict, your pretty string is parsed,
    converted into a proper dict data structure, and then the next time you
    'puts' it, you get back the default string representation of a dict
    data structure, not the original string you began with.

    I had to make a proc that "beautifies" the printing of the main dict.

    https://wiki.tcl-lang.org/page/pdict%3A+Pretty+print+a+dict?R=0&O=pdict

    It wasn't very easy to write and it's only good for that one dict and
    its own unique structure. Doing the same with namespaces would be a
    lot easier and it would work with all dict-replacing namespaces.


    Plus, if you wanted to eventually save your state across exits and
    reload where you were, the dict method gives you that single data
    structure to save/restore. The plural namespaces means you need extra
    work to extract and serialize to disk the namespaces and later restore
    them all upon reload.

    That is a good point, but

    1. I feel that coding with dicts is by itself a lot of "extra work"
    because their structure is trickier.

    Not if you use the proper "create" procs (i.e., [dict create]). Then
    there's nothing 'tricky' at all.

    The command lines are longer and look a bit too fragmented to me,

    This is true, but you realize this is Tcl, you can 'adjust' that fact
    if you like:

    % dict get $dict key1
    a string
    % interp alias {} dget {} dict get
    dget
    % dget $dict key1
    a string
    %


    and inserting lists and sublists is a pretty good minefield so it's
    very easy to make a mistake

    If you use the creation procs, and the setting procs, instead of trying
    to create strings that are proper as dicts, then it is almost
    impossible to "make a mistake", and usually those mistakes that are
    made are immediate syntax errors just after making a code change, not
    hidden mines waiting to blow up a week from now.

    and I have to be printing and checking them all the time.

    Use the proper creation and update procs and you won't have to be
    "printing and checking them all the time".

    I feel very tempted to rewrite everything with namespaces now because
    it will be a lot easier to manage.

    That's up to you, and you can certianly do so. But you'll likely
    encounter some other issue with that path that brings back the
    complexity issues you think you are avoiding, just wearing a different
    hat this time.

    Bzzt, please try again.

    That is because you tried to do string interpolation to store the
    array. Don't do that. This works just fine:

    $ rlwrap tclsh
    % array set aa {item1 a}
    % set dd [dict create]
    % dict set dd array1 [array get aa]
    array1 {item1 a}
    % dict get $dd array1
    item1 a

    Granted, what is stored is the string representation of the array, but
    once stored, you can then 'dict get' against it:

    % dict get $dd array1 item1
    a

    Thank you for the lesson, but I find that a little too convoluted. It
    seems to me that you in fact did not store an array in the dict but
    rather converted it into a list so it would be admitted by the dict's doorman/bouncer.

    Yes, I said so in the "Granted, what is stored is the string
    representation of the array," statement. You can't physically store
    "an array" in a dict, for the same reason why you can't actually pass
    an array to a proc as a parameter. A design decision with how array's
    were handled way back in Tcl's very early days resulted in array's
    being 'oddball' that way.

    But you'll note I stored the array using the proper dict update proc,
    not by trying to do 'set dd "$dd array1 [array get aa]"' via string interpolation. If you have lots of those throughout your code, you
    have a minefield just waiting to be stepped on some day.

    In any case, if you are not using traces, the subtle distinction
    hardly matters.

    Not yet.

    The question of course is, do you plan to use traces?

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rich@21:1/5 to Luc on Sun Jan 7 23:10:47 2024
    Luc <[email protected]d> wrote:
    On Sun, 7 Jan 2024 21:00:37 -0000 (UTC), Rich wrote:

    Watch:

    $ rlwrap tclsh
    % set somevar "a string"
    a string
    % set dict "key1 $somevar"
    key1 a string
    % dict get $dict key1
    missing value to go with key
    %
    vs:
    % set dict [dict create key1 $somevar]
    key1 {a string}
    % dict get $dict key1
    a string
    %

    Well, yes, but I read the upper part of your example, found it strange
    and did it differently:

    % set somevar "a string"
    % dict create dict {}
    % dict set dict key1 $somevar
    key1 {a string}
    % dict get $dict key1
    a string

    That would be my way of doing it.

    Except that is not what you posted at the start of this thread:

    dict set ::TabControl $tabID "
    [list TCTabPosition $position]
    [list TCDirectory $dir]
    [list TCViewMode $::GUIoptions_DefaultLayout]
    "

    You are trying to set the key in $tabID to a string created by
    concatenating lists through string interpretation. Which is the point
    I'm trying to make:

    Don't create a dict, or a list, by beginning with a double quoted
    string: " ... "

    Doing so can often get you a quick ride to quoting hell: https://wiki.tcl-lang.org/page/Quoting+hell

    [dict create ...] for dicts
    [list ...] for lists

    I make mistakes with lists often and with dicts too because dicts are
    (or may be) lists of lists. And the [list] construct doesn't always
    help in my case. PEBKAC.

    If you try to use [list ...] inside double quoted strings to
    concatenate things, then sometimes it works, sometimes it will blow up.

    Don't try to create a list by beginning something with double quote
    ("). When you find yourself doing so, take a step back and work out
    how to use [list] (nested if necesssary) or [dict create] (nested if
    necessary) to create the list of dict, without needing to surround it
    with double quotes.

    The question of course is, do you plan to use traces?

    Maybe. Some idea crossed my mind. If one tab is displaying a directory
    and the user opens another tab for the same directory and makes changes
    in it (e.g. create a new directory) I thought that maybe I should let
    all tabs that display that one directory know about it immediately. But

    1. I keep control of everything by tabID not by path and there are no variables concerning the content of directories except one array that
    is also identified by tabID. The idea behind that is, for example, when selecting an entry (for deletion for example) I used to identify the
    entry's path with [$widget get $currentline.0 "$currentline.0 lineend]
    but I scrapped that for cosmetic reasons. I am giving the user the
    option to list empty directories as such, for example:

    somedir
    anotherdir
    thisother (empty)
    someother

    So I am using an array of line numbers and filenames instead so the
    displayed entry doesn't have to be exactly the same as the actual
    filename.

    Yes, I realize it is dangerous, but unless the tab display "refreshes" between the user's selection and the delete operation, the selection
    and the operation are safe, and "getting" the filename from the widget display wasn't terribly safe either.

    2. When I create files and directories in my application, other file
    managers pick them up and display them immediately. They are really
    fast so I'm guessing they keep monitoring the current tab's directory
    every so many milliseconds and I should probably do the same.

    Unlikely. More likely they are using the kernel inotify event api:

    https://wiki.tcl-lang.org/page/tcl%2Dinotify

    Polling every few ms is a great way to burn battery on a laptop/phone
    when the same is not plugged into a charger.

    You, of course, still have to know what to change in the UI based on
    what the kernel tells you was updated.

    None of this (so far) needs Tcl's traces.

    But then my "selection" is not safe anymore because the listing and
    line numbers may change under the user's feet during a delete
    operation. I still don't know how to avoid that possibility. Ideas
    are very welcome.

    One possibility might be to have the active 'tab' broadcast a change
    event via [event generate] and have the tabs all watching that event
    stream, and making appropriate updates to their own displays based
    upon the changes broadcast via the events.

    Another possibility is for the active tab to update all the underlying
    data of all the other tabs, but that feels more complex than having
    each tab do its own updates from a "something changed" message.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Luc@21:1/5 to Rich on Sun Jan 7 19:20:27 2024
    On Sun, 7 Jan 2024 21:00:37 -0000 (UTC), Rich wrote:

    Watch:

    $ rlwrap tclsh
    % set somevar "a string"
    a string
    % set dict "key1 $somevar"
    key1 a string
    % dict get $dict key1
    missing value to go with key
    %
    vs:
    % set dict [dict create key1 $somevar]
    key1 {a string}
    % dict get $dict key1
    a string
    %

    Well, yes, but I read the upper part of your example, found it strange
    and did it differently:

    % set somevar "a string"
    % dict create dict {}
    % dict set dict key1 $somevar
    key1 {a string}
    % dict get $dict key1
    a string

    That would be my way of doing it.

    [dict create ...] for dicts
    [list ...] for lists

    I make mistakes with lists often and with dicts too because dicts are
    (or may be) lists of lists. And the [list] construct doesn't always
    help in my case. PEBKAC.


    I feel very tempted to rewrite everything with namespaces now because
    it will be a lot easier to manage.

    That's up to you, and you can certianly do so. But you'll likely
    encounter some other issue with that path that brings back the
    complexity issues you think you are avoiding, just wearing a different
    hat this time.

    I've done it already and I like it so far. I think things look cleaner
    and simpler already. There is one particular section that changed
    radically for the better, very neat, so I am even more optimistic about
    it. Let's see.

    Either way, I really appreciate your input because you are a lot more experienced than me (not exactly a challenge anyway) and this is a
    "public" project, i.e. something that will be freely available and may
    have to be maintained by someone else in the future. I hope I am not
    leaving a legacy of very horrible code to whoever decides to pick it up.



    The question of course is, do you plan to use traces?

    Maybe. Some idea crossed my mind. If one tab is displaying a directory
    and the user opens another tab for the same directory and makes changes
    in it (e.g. create a new directory) I thought that maybe I should let
    all tabs that display that one directory know about it immediately. But

    1. I keep control of everything by tabID not by path and there are no
    variables concerning the content of directories except one array that
    is also identified by tabID. The idea behind that is, for example, when selecting an entry (for deletion for example) I used to identify the
    entry's path with [$widget get $currentline.0 "$currentline.0 lineend]
    but I scrapped that for cosmetic reasons. I am giving the user the
    option to list empty directories as such, for example:

    somedir
    anotherdir
    thisother (empty)
    someother

    So I am using an array of line numbers and filenames instead so the
    displayed entry doesn't have to be exactly the same as the actual
    filename.

    Yes, I realize it is dangerous, but unless the tab display "refreshes"
    between the user's selection and the delete operation, the selection
    and the operation are safe, and "getting" the filename from the widget
    display wasn't terribly safe either.

    2. When I create files and directories in my application, other file
    managers pick them up and display them immediately. They are really
    fast so I'm guessing they keep monitoring the current tab's directory
    every so many milliseconds and I should probably do the same. But then
    my "selection" is not safe anymore because the listing and line numbers
    may change under the user's feet during a delete operation. I still
    don't know how to avoid that possibility. Ideas are very welcome.

    I still haven't written any of that. I will think harder about it and
    test it extensively. What I already have is a "sandbox" mode that lets
    me choose a directory and the app will refuse to do anything potentially "regrettable" outside of the sandbox.

    TL;DR: no, I probably won't need traces. Probably.


    --
    Luc


    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Luc@21:1/5 to All on Mon Jan 8 04:45:12 2024
    I forgot to comment on this:


    The command lines are longer and look a bit too fragmented to me,

    This is true, but you realize this is Tcl, you can 'adjust' that fact
    if you like:

    % dict get $dict key1
    a string
    % interp alias {} dget {} dict get
    dget
    % dget $dict key1
    a string
    %

    I find this a bad idea. A long time ago, when I had just learned Tcl,
    I loved that idea and used it a lot. I made a long script that I always
    sourced in my scripts and I still source in Tkcon (which is of course
    both my Tcl testing ground and my trusty pocket calculator thanks to
    'unknown') with a ton of such abbreviations and enough syntax sugar to
    have diabetes.

    It took a while but I eventually realized that I was conditioning myself
    to speak a Tcl dialect that others would likely understand but with some considerablle effort, and that would certainly annoy the hell out of a
    lot of people if I ever shared any code in that pidgin Tcl. I was
    basically writing in Perl.

    I may still use my sugary syntax on Tkcon once in a while, but I've
    acquired the habit of avoiding those tricks so I don't alienate myself
    from The Elements of Style. As this is a "public" project that others
    may have to maintain someday, I will most certainly do my best to speak
    The King's Tcl.

    But my best is MY best, you know...

    --
    Luc


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