argumentstclprocsplatdouble-splat

two level splatter TCL


If I have a procedure or a command in TCL, with variable number of arguments, one can use, if a list's elements are as an input, the "splatter" operator, for example:

set a [list "ko" ]
set m [ list "ok" "bang" ]
lappend a {*}$m

But, what if I want to "twice splatter"? I.e., flatten 2 levels? Using it twice, in sequence, does not work:

set a [list "ko" ]
set m [ list [ list "ok" ] [ list "bang" ] ]
lappend a {*}{*}$m

Will error out on extra character.


Solution

  • You've noticed that {*} (deliberately) doesn't go two steps deep.

    Your particular example isn't very indicative of the problem, so I suggest this:

    set a [list "ko" ]
    set m [list [list "a b" "c d"] [list "e f" "g h"]]
    lappend a {*}$m
    

    Here, we get a set to ko {{a b} {c d}} {{e f} {g h}}. Which isn't what you wanted. But we can instead do this:

    lappend a {*}[concat {*}$m]
    

    Which gives this: ko {a b} {c d} {e f} {g h}. That looks right.


    But are we really doing the right thing here? Let's poke inside with our super-secret introspector, the representation command:

    % tcl::unsupported::representation $m
    value is a list with a refcount of 4, object pointer at 0x10085ec50, internal representation 0x103016790:0x0, string representation "{{a b} {c d}}..."
    % tcl::unsupported::representation [concat {*}$m]
    value is a string with a refcount of 1, object pointer at 0x10085de10, internal representation 0x1030052d0:0x10085f190, string representation "{a b} {c d} {..."
    

    Uh oh! We've lost the list-ness. It's not a catastrophe, but it isn't what we wanted. We instead should really do:

    foreach sublist $m {
        lappend a {*}$sublist
    }
    

    Well, that's more code but preserves list-ness (which would be good if you happened to have precious types on the leaves; core Tcl doesn't have such precious types, but some extensions do).

    We can compare the timings:

    % time {
        set a [list "ko" ]
        set m [list [list "a b" "c d"] [list "e f" "g h"]]
        lappend a {*}[concat {*}$m]
    } 10000
    2.852789 microseconds per iteration
    % time {
        set a [list "ko" ]
        set m [list [list "a b" "c d"] [list "e f" "g h"]]
        foreach sublist $m {
            lappend a {*}$sublist
        }
    } 10000
    4.022959 microseconds per iteration
    

    Oh…

    % time {apply {{} {
        set a [list "ko" ]
        set m [list [list "a b" "c d"] [list "e f" "g h"]]
        lappend a {*}[concat {*}$m]
    }}} 10000
    2.4486125 microseconds per iteration
    % time {apply {{} {
        set a [list "ko" ]
        set m [list [list "a b" "c d"] [list "e f" "g h"]]
        foreach sublist $m {
            lappend a {*}$sublist
        }
    }}} 10000
    1.6870501 microseconds per iteration
    

    Hah! The type-correct one is better in a procedure(-like context).