TIP 212: Temporarily Opening out a Dictionary

State:		Final
Type:		Project
Tcl-Version:	8.5
Vote:		Done
Author:		Donal K. Fellows <[email protected]>
Created:	11-Aug-2004
Keywords:	tcl, dict, update, script
Tcl-Ticket:	1008768


This TIP proposes a new subcommand of dict that associates variables with dictionary entries while a Tcl script (the "body" of the command) runs.


The standard set of operations provided by the dict command (see [111] and [163]) give a fairly rich set of operations, but there are always further things that script authors want to do. Instead of ever-expanding numbers of subcommands offering tiny variants on existing operations, this TIP provides a generalized mechanism for associating variables and dictionary entries for the duration of a script. This makes writing arbitrary complex manipulations of dictionaries much simpler.

Proposed Change

This TIP proposes two new subcommands to dict, update and with, with the following syntaxes and semantics.

The 'dict update' Subcommand

dict update dictVarName dictKey varName ?dictKey varName ...? bodyScript

In prose, the first argument is the name of a variable containing a dictionary that will be updated, the last argument is a script to be executed with the variables set up and whose effects are expected to be to update the associated variables, and the other arguments between those two (of which there must be an even number, with a minimum of two) are a list of dictionary keys and the names of variables with which to associate those keys.

The semantics of the command are this:

  1. For each dictionary key (dictKey), the value from the dictionary contained in the variable named by dictVarName which that key maps to will be written to the variable associated with that key (varName). If the key is not present in the dictionary, the variable will be unset.

  2. The script (bodyScript) is executed.

  3. The dictionary is read out of dictVarName again and, for each dictKey, the varName variable is consulted and the contained value placed in the dictionary (if varName is unset, any existing mapping for dictKey will be removed.) Finally, the resulting dictionary value is written back to dictVarName.

    If the dictVarName variable was unset (without being set to a new value) or otherwise rendered unreadable during the execution of bodyScript, the updating of the keys and writing back of the resulting dictionary to dictVarName is skipped. Note that it is only an unreadable dictVarName that is treated this way; it is an error for the variable to contain a value that is not a valid dictionary.

Note that dictVarName is read twice (each time, the value read must be a valid dictionary or the variable must be unset, which is interpreted as an empty dictionary) and (assuming the second read succeeds and returns a valid dictionary value) written once, and that no traces are set up; dictVarName is completely independent of the varName_s during the execution of _bodyScript.

Also note that step 3 is carried out even if bodyScript completes with a result code other than TCL_OK, and that the result code of the overall command will be the result code of the script unless TCL_ERROR was the result of the write-back stage, which might happen if the variable dictVarName is updated (during the execution of bodyScript) to contain a non-dictionary value, or if the variable dictVarName is either unset or cannot be written to (which can happen if a write trace throws an error); note that this mandates that any errors from the bodyScript are lost if the write-back phase also throws an error. If the result code is TCL_OK, the result string/object will be whatever the result string/object of bodyScript was.

The 'dict with' Subcommand

dict with dictVarName ?key ...? bodyScript

This is a shorthand version of dict update (named after the Pascal with keyword) which maps all of the keys in the dictionary in the variable dictVarName to variables during the evaluation of bodyScript. Note that it builds a list of all the keys in the dictionary before bodyScript runs and only those variables which are created are mapped back at the end; if extra keys are defined in the dictionary in the variable during the execution of bodyScript, they will be left alone when bodyScript finishes.

The variables will be written to and read from in the natural order of the keys of the dictionary in the variable dictVarName when the dict with command starts.

When a key (or several key_s) is provided, it describes a path into a nested set of dictionaries (much like with dict set) where the selected leaf indicates a dictionary that is to be opened out. When writing back, non-existence of any key on the path is treated the same as if _dictVarName itself didn't exist.


A somewhat-less-efficient version of dict set:

 dict update someVar $key local {
     set local $value

Sorting a list inside a nested dictionary:

 dict update someVar OuterKey temp {
     dict update temp InnerKey temp2 {
         set temp2 [lsort $temp2]

"Renaming" a key:

 dict update someVar oldKey foo newKey bar {
     set bar $foo
     unset foo

Opening out a nested dictionary:

 dict update someVar OuterKey temp {
     dict with temp {
         # Some script goes in here...
 # Alternative version using a key with 'dict with'...
 dict with someVar OuterKey {
     # Some script goes in here...  

Reference Implementation

See SF patch 1008768http://sf.net/tracker/?func=detail&aid=1008768&group_id=10894&atid=310894 .


This document has been placed in the public domain.