Version 21 of Let's assign with let

Updated 2018-05-01 10:59:08 by pooryorick

Let's assign with let presents techniques to assign data to multiple variables. For example, one might transpose the value of two variables.

See Also

L , by Larry McVoy

Let

proc let { args } {
    if { [llength $args ] == 2 } {
        if [string equal [ lindex $args 1 ] "++" ] {
            set result [ uplevel incr [ lindex $args 0 ] ]
        } elseif [string equal [ lindex $args 1 ] "--" ] {
            set result [ uplevel incr [ lindex $args 0 ] -1 ]
        } else {
            set result [ uplevel set "$args" ]
        }
    } else {
        regexp {([^=:+\-*/&|@]*)([:+\-*/&|@]?)([@]*)=(.*)} $args -> vars op optional rest
        if ![ info exists op ] {
            return -code error -errorcode 1 "no valid assignment operator in $args"
        }
        switch -- $op {
            : {
                if [llength [info commands [lindex $rest 0]]] {
                    set result [uplevel $rest]
                } else {
                    set result $rest                ;# this should always work...
                }
                if { "$optional" == "@" } {
                    set max [ llength $result ]
                    foreach var $vars res $result {
                        uplevel 1 [ list set $var $res ]
                    }
                } else {
                    foreach var $vars {
                        set result [ uplevel [list set $var $result] ]
                    }
                }
            }
            @ {
                if { "$optional" == ":" } {
                    set rest [uplevel $rest]
                }
                set max [ llength $rest ]
                if { $max == 1 } {
                    eval set rest $rest
                    set max [ llength $rest ]
                }
                foreach var $vars res $rest {
                    set result [ uplevel 1 [ list set $var $res ]]
                }
            }
            + - - - * - / - & - | { 
                foreach var $vars {
                    set result [ uplevel set $var \[ expr \$$var $op ( $rest ) \] ]
                }
            }
            = -
            default {
                if { [ catch { set result [ uplevel expr $rest ] } ] } {
                    set result $rest              ;# this should always work...
                }
                foreach var $vars {
                    set result [ uplevel [list set $var $result] ]
                }
            }
        }
        }
        return $result
}

Example: Transposing two variables:

let a b @= $b $a

Which, by the way, also allows:

let a b c = 1               ;#this sets a b and c to 1
let a b c = 1 + 4           ;#"=" uses expr to process the value to assign
let a b c += 1              ;#computed assignments are allowed, +-*/&| supported
let a b c := info commands  ;#uses eval to process right side
let a b c @= 1 2 3          ;#instead of assigning the list {1 2 3} to a b and c,
                            ;#it instead assigns the elements in order, resulting
                            ;#in a getting the value 1, b getting 2 and so on.
let a b c @:= info commands ;#uses eval to get result and uses @= for assignment
let a ++                    ;#incr and
let a --                    ;#decr are supported.

RS The code fragment (appears twice above)

set i 0
foreach var $vars {
    uplevel set $var [ lindex $result $i ]
    incr i
}

seems like it can be replaced by

foreach var $vars res $result {
   uplevel 1 [list set $var $res]
}

- if foreach can do a job once, it might as well do it twice ;-) A variable saved, and possibly safer with the list wrapper...

Larry Smith: I'd clean forgotten that feature. Yes, it's much more elegant that way. The above code has been corrected.

foreach

My suggestion will swap a-b and vice versa:

foreach {a b} [list $b $a] {}

Although let has many other features of course.


aspect 2015: For interactive use I like to have the following two definitions active (from .tclshrc):

# let <varName> <cmd> <arg>....
proc let {name args} {
    tailcall ::set $name [uplevel 1 $args]
}
interp alias {} = {} expr

The mostly saves me having to contort my fingers to write expr { and }. The specific combination using = as a pun for expr I find more Tclish than = as an always-present keyword argument.

let pi = 4*atan(1)

Of course the resulting code is suboptimal and dangerous (Brace your expressions!), but it is a lot more fun to write. The only problem is that before publication I need to clean up, but that's a few editor macros away.

You will find some of the toys I paste to the wiki use this combination, where I feel readability trumps robustness.