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] } } }
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.