Search code examples
tcl

Neatly listing values in multiple lines


How does one list values in multiple lines without a backslash at the end of each line?

One can't create a list in multiple lines without having a backslash at the end.
For example, the following (wrong) code:

set pets [list 
    cat
    dog
    elephant
]

gives an error:

invalid command name "cat"
    while executing
"cat"
    invoked from within
"set pets [list
        cat
        dog
        elephant
]"

It can be fixed by appending a backslash at the end of the line:

set pets [list \
    cat \
    dog \
    elephant \
]

Which is ugly and prone to errors.

Please note that:

  • I'm aware of using the curly braces ({ & }), but it doesn't allows executing commands and also keeps redundant whitespace characters.
  • Any other command may be used (e.g. dict create), not only list as in my example.

Using Tcl 8.5


Solution

  • Tcl uses newline (and semicolon) as a command separator. This is a core part of the basic syntax; you can't work around it so you must use either double quotes or braces to avoid backslash-itis. Let's look at the possibilities (remember, list separators can be any non-empty whitespace sequence).

    Ugly, error prone list with backslashes

    set pets [list \
        cat \
        dog \
        $elephant \
    ]
    

    With braces, no substitutions

    set pets {
        cat
        dog
        $elephant
    }
    

    (Note that in above, $elephant is just a sequence of characters, not a variable read.)

    With double quotes, substitutions but be careful!

    set pets "
        cat
        dog
        $elephant
    "
    

    By “be careful!” I mean that where you have a multi-word member of the list, you need an inner [list …] or other quoting:

    set pets "
        cat
        dog
        [list $elephant]
        [list "elephant's child"]
    "
    

    But this would be true with the list+backslashes at the top.

    Using subst

    set pets [subst {
        cat
        dog
        $elephant
        "elephant's child"
    }]
    

    I might “clean that up” (and avoid other potential problems) with:

    set pets [list {*}[subst {
        cat
        dog
        [list $elephant]
        [list "elephant's child"]
    }]]
    

    But frankly, if things are getting really complex then I actually do this:

    Construct with several commands

    set pets {
        cat
        dog
    }
    lappend pets $elephant "elephant's child"
    

    No point in bashing yourself over the head to use one command when two or more will do everything with fewer problems.