dict update

dict update maps values in a dictionary to variables, evaluates a script, and reflects any changes to those variables back into the dictionary.

Synopsis

dict update dictionaryVariable key varName ?key varName ...? body

Description

When body is evaluated, the value of each key in dictionaryVariable is assigned to the corresponding varName. If key doesn't exist in dictionaryVariable, the corresponding varName is not set, and is unset if was set prior to the call to dict update. When the evaluation of body is complete, the values of any varName variables are reflected back into the dictionary, regardless of the exit status of body. If the evaluation of body causes dictionaryVariable to be unset, it is not recreated.

After the evaluation of body, dict update does not unset any varName variables it created, modified, or unset. This can be used to "export" values in a dictionary as variables in the current level, leading to an idiom in which body is empty:

dict update some_data key1 varname1 key2 varname2 {}

Where it is desirable insulate the current level from changes by dict update, use a procedure, lambda, or apply to isolate it.

Examples

HaO 2012-07-20: As I did not understand what it does, I try an example:

Dict update is like a dict set with a proc:

% set d {key1 value1 key2 value2}
key1 value1 key2 value2
% dict update d key1 varKey1 {
    append varKey1 new
  }
value1new
% set d
key1 value1new key2 value2

There might be multiple keys updated simultaneously:

% set d {key1 value1 key2 value2}
key1 value1 key2 value2
% dict update d key1 varKey1 key2 varKey2 {
    append varKey1 new
    append varKey2 new
  }
value2new
% set d
key1 value1new key2 value2new

Keys might be checked for existence and set if they don't exist:

% set d {key1 value1 key2 value2}
key1 value1 key2 value2
% dict update d key3 varKey3 {
    if {![info exists varKey3]} {
       set varKey3 new
    }
  }
new
% set d
key1 value1 key2 value2 key3 new

Keys might be deleted:

% set d {key1 value1 key2 value2}
key1 value1 key2 value2
% dict update d key1 varKey1 {
    unset varKey1
  }
% set d
key2 value2

Application idea by George Petasis on clt:

lappend for sublist:

% set d {key1 {subkey11 value11}}
key1 {subkey11 value11}
% dict update d key1 varKey1 {
    dict lappend varKey1 subkey11 new
  }
subkey11 {value11 new}
% set d
key1 {subkey11 {value11 new}}

Try to put this in a proc:

proc lasubdict {dictname key subkey value} {
    upvar 1 $dictname dictvar
    dict update dictvar $key subdict [list dict lappend subdict $subkey $value]
}
% set d {key1 {subkey11 value11}}
% lasubdict d key1 subkey11 new
subkey11 {value11 new}
% set d
key1 {subkey11 {value11 new}}

Maybe, this is implemented in a more efficient manner with dict with:

proc lasubdict {dictname key subkey value} {
    upvar 1 $dictname dictvar
    dict with dictvar $key [list lappend $subkey $value]
}

Or just dict set:

proc lasubdict {dictname key subkey value} {
    upvar 1 $dictname dictvar
    dict set dictvar $key $subkey [lappend [dict get $dictvar $key $subkey] $value]
}

AMG: [lappend] is inappropriate in the final code snippet above because its first argument must be the name of a variable. Instead write:

proc lasubdict {dictname key subkey value} {
    upvar 1 $dictname dictvar
    dict set dictvar $key $subkey [concat [dict get $dictvar $key $subkey] [list $value]]
}

Here is a more powerful version that allows arbitrary levels of nesting and appends any number of list elements:

proc lasubdict {dictname keypath args} {
    upvar 1 $dictname dictvar
    dict set dictvar {*}$keypath [concat [dict get $dictvar {*}$keypath] $args]
}

AMG: Adding to what George Petasis was reported to have said above, [dict update] enables manipulation of nested dicts using methods that do not natively support nested dicts.

For example, the following:

dict set dictVar $topKey [concat [dict get $dictVar $topKey $innerKey] [list $newListValue]]

can also be written:

dict update dictVar $topKey elem {
    dict lappend elem $innerKey $newListValue
}

This is useful to extend the capability of [dict append], [dict incr], and [dict lappend], as well as to grant dict capability to commands which only operate directly on variables.

Ironically, [dict update] does not itself support nested dicts. However, it can be (ahem) nested:

dict update dictVar $topKey topElem {
    dict update topElem $middleKey middleElem {
        dict lappend middleElem $innerKey $newListValue
    }
}

does the same as:

dict set dictVar $topKey $middleKey [concat [dict get $dictVar $topKey $middleKey $innerKey] [list $newListValue]]

Here is an alternative to [dict update] that supports arbitrary nesting:

proc dictUpdatePath {dictVarName args} {
    # Check argument count.
    if {[llength $args] < 3 || !([llength $args] & 1)} {
        error "wrong # args: should be \"[lindex [info level 0] 0] dictVarName\
                keyPath varName ?keyPath varName ...? body\""
    }

    # Link to caller's dictionary variable.
    upvar 1 $dictVarName dictVar

    # Prepare caller variables.
    foreach {keyPath valVarName} [lrange $args 0 end-1] {
        upvar 1 $valVarName valVar
        if {[eval [list dict exists $dictVar] $keyPath]} {
            set valVar [eval [list dict get $dictVar] $keyPath]
        } else {
            unset -nocomplain valVar
        }
    }

    # Invoke script in caller stack frame.
    set result [uplevel 1 [lindex $args end]]

    # If dictionary still exists, update it to reflect new values of variables.
    if {[info exists dictVar]} {
        foreach {keyPath valVarName} [lrange $args 0 end-1] {
            upvar 1 $valVarName valVar
            if {[info exists valVar]} {
                eval [list dict set dictVar] $keyPath [list $valVar]
            } else {
                eval [list dict unset dictVar] $keyPath
            }
        }
    }

    # Return the result of the script.
    return $result
}

Let's try it out:

dictUpdatePath dictVar [list $topKey $middleKey] elem {
    dict lappend elem $innerKey $newListValue
}

Or even better:

dictUpdatePath dictVar [list $topKey $middleKey $innerKey] elem {
    lappend elem $newListValue
}

By the way, [dictUpdatePath] will work in Tcl 8.4 if forward-compatible dict has been loaded.

[dict with] can be used as an alternative to [dict update], plus it supports arbitrary nesting. The downside is it converts all keys to/from variables, not just the one or two that are desired, and it does not allow the variable names to be different from the key names.

dict with dictVar $topKey $middleKey {
    lappend $innerKey $newListValue
}