named parameters

See Also

Handling command line options with dict
Similar content to this page. The two pages could probably be merged.

Description

One simple approach to processing inputs is to treat $args as a dictionary, unpacking the contents into local variables. This approach turns argument parsing into a one-liner.

proc dostuff {p1 p2 args} {
   dict with args {}
   do stuff with $p1 $p2 and $some $other $values
}

As RS points out, this approach is given in Practical Programming in Tcl and Tk:

proc foo args {
    # first assign default values...
    array set options {-bar 1 -grill 2 -verbose 0 ...}
    # ...then possibly override them with user choices
    array set options $args
    ....
} 

Sometimes it makes sense to use $args to collect a sequence array of values, in which case the options can be one of the positional parameters, but at a cost of slightly more inconvenience to the caller:

proc dostuff {options p1 p2 args} {
   [dict with] options {}
   do stuff with $p1 $p2 and $some $other $values
}

To restrict what is unpacked into a variable, use dict update:

proc dostuff {p1 p2 args} {
   dict update args name name score score {}
   do stuff with $name and $score
}

There's often no need to explicitly check that a mandatory argument has been provided in the options, as the interpreter return an error when the script attempts to access an undefined variable. If an explicit early check turns out to be needed, an identity command can be put to good effect:

proc dostuff {p1 p2 args} {
   dict update args name name score score {}
   #check that $score was provided
   lindex $score
   ...
   do stuff with $score
}

aspect: an aspect (no pun intended) of this approach that bugs me deeply is that mis-spelled arguments are silently ignored, leaving a hard-to-diagnose bug:

% dostuff foo bar mane Albert   ;# I meant "name"

This is a violation of the principle make wrong code look wrong that is (incidentally) common to JSON configuration files. There are cases when "ignore unrecognised keys" is a good idea, but I really like procedures to raise meaningful errors when given the wrong keywords. OTOH, the approach described here (or something much like it) is used to good effect in (strongly coupled) parts wibble.

PYK 2015-11-04: Here's a helper procedure to address that:

proc checkopts {args allowed} {
    dict for {key val} $args {
        if {$key ni $allowed} {
            return -level 2 -code error [list {bad argument} $key]
        }
    }
}

An extended Example

proc example {a b args} {
    if {[llength $args] == 1} {
        set args [lindex $args 0] ;# work with unexpanded args
    }
  
    # establish defaults
    set args [dict merge {default value} $args]
  
    # use elements from dict
    fn [dict get $args parameter]
  
    # (dangerously) use args dict as variables
    dict with args {
        lappend parameter END
    }

    # less dangerously, use dict update
    dict update args parameter parameter {
        lappend parameter END
    }


}

Alternatively, one can use array to the same effect:

proc example {a b args} {
    if {[llength $args] == 1} {
        set args [lindex $args 0] ;# work with unexpanded args
    }
  
    # establish defaults
    array set A {default value}
    # pour in supplied args
    array set A $args
  
    # use elements from A
    fn $A(parameter)
  
    # array elements are variables, so there is no need to unpack them explicitly.
    lappend A(parameter) END
}

slebetman: Also see optproc for another take on named parameters.