How to pass arrays

When programming, one has several choices for getting information into a procedure:

  • pass an argument value at invocation
  • pass a variable reference (usable via uplevel or upvar) at invocation
  • access a local variable
  • access a non-local (global, environment, outside the frame) variable
  • request input from the user or from a file/socket/etc.

In Tcl, variables containing strings (whether in list, handle, or normal string format) are typically passed by value. This means that the called procedure never really knows the name of the variable containing the value - and thus it cannot directly modify that value. Instead, most Tcl scripting looks like:

 set val [lindex $val 2]

In other words, the value of a variable changes after the command has calculated a new value.

Entire arrays, however, are not currently able to be passed by value to a Tcl procedure (despite the damage this does to the EIAS dogma). This is due to Tcl currently passing only 'string' type data as arguments, and arrays not having a simple string notation for their value.

Instead, there are two choices for passing arrays both "into" and "out of" proc-s:

  • serialization (generally converting the values of an array into a list with "array get ...")
  • local proxying of an external variable by use of upvar or occasionally uplevel

If 8.5 or greater is available, a third alternative is to

  • use a dict instead of an array.

If you're willing to use extensions, a whole range of additional "instead of an array, use ..." possibilities:

Examples

  • Create an array from pairlist: array set A {city Hamilton state TX zip 34567}
  • Serialize an array to pairlist: set L [array get A]
  • Copying an array: array set B [array get A] [but we have to show explicitly how this relates to proc-to-proc communication]

Example of passing an array by serializing the array to pair list

 proc top {} {
        array set thisarr {
                en "Hi everybody"
                de "Guten Morgen allerseits"
        }

        puts "top: english=$thisarr(en)"
        puts "top: german=$thisarr(de)"

        set newarrlist [bottom [array get thisarr]]

        array unset thisarr
        array set thisarr $newarrlist

        puts "top: latin=$thisarr(la)"
 }

 proc bottom {arrlist} {
        array set thatarr $arrlist

        puts "bottom: english=$thatarr(en)"
        puts "bottom: german=$thatarr(de)"

        set thatarr(la) "Salvete omnes"

        return [array get thatarr]
 }

Note that the cost of [array get] and [array set] is not as bad as it looks. Because in the implementation objects are reference-counted and the intermediate is represented as a real list, the content of the array keys and values are never actually copied. Only the array structure itself is recreated by [array set].


Example of local proxying of an external variable

 proc myparray {ea} { 
      upvar $ea a 
      puts "-------"
      parray a; # or any other manipulation on $a(..)
      puts "-------"
 }

 myparray ::env

Removing keys with empty values from an array: Note that the array name is passed in, associated with local variable arr. The unsetting of elements with empty value, done in arr, in fact happens in the caller's original array.

 proc cleanArray arrName {
     upvar 1 $arrName arr
     foreach key [array names arr] {
         if {$arr($key) == ""} {unset arr($key)}
     }
 }

KBK (16 April 2002) - In Tcl 8.3 and beyond, it's much faster to say, simply:

    array unset arr

rather than looping through the result of [array names]. In earlier versions, you can say

    unset arr
    array set arr {}

but that has the disadvantage of messing up any traces that are extant on the array. RS: Indeed, but this clears the whole array - the specified task was to remove only those elements which have empty values.


ken: How do i create a namespace variable for array set variables? Is it possible ? Cause i am creating a couple of procedures which need access to their global variables?

Lars H: Accessing namespace variables work the same for arrays as for ordinary variables; one can use variable, global, or the fully qualified variable name. Does this answer your question?

NEM: To illustrate:

 namespace eval foo {
     variable myarray
     array set myarray { ... }
     proc bar value {
         variable myarray
         set myarray(element) $value
     }
 }

It's important to use "variable" to declare the variable in the right namespace, to avoid the dangers of creative writing.