40.2.3.8 User Defined Procedures

Tcl provides a way of creating new commands, called procedures, that can be executed in scripts. The arguments of a procedure can be call-by-value or call-by-reference, and there is also a facility for creating new user defined control structures using procedures.

A procedure is declared using the proc command:

     proc name argList body

where the name of the procedure is name, the arguments are contained in argList and the body of the procedure is the script body. An example of a procedure is:

     proc namePrint { first family } {
         puts "My first name is $first"
         puts "My family name is $family"
     }

which can be called with

     namePrint Tony Blair

to produce the output:

     My first name is Tony
     My family name is Blair

A procedure with no arguments is specified with an empty argument list. An example is a procedure that just prints out a string:

     proc stringThing {} {
         puts "I just print this string"
     }

Arguments can be given defaults by pairing them with a value in a list. An example here is a counter procedure:

     proc counter { value { inc 1 } } {
         eval $value + $inc
     }

which can be called with two arguments like this

     set v 10
     set v [counter $v 5]

which will set variable v to the value 15; or it can be called with one argument:

     set v 10
     set v [counter $v]

in which case v will have the value 11, because the default of the argument inc inside the procedure is the value 1.

There is a special argument for handling procedures with variable number of arguments, the args argument. An example is a procedure that sums a list of numbers:

     proc sum { args } {
         set result 0;
     
         foreach n $args {
          set result [expr $result + $n ]
         }
     
         return $result;
     }

which can be called like this:

     sum 1 2 3 4 5

which returns the value 15.

The restriction on using defaulted arguments is that all the arguments that come after the defaulted ones must also be defaulted. If using args then it must be the last argument in the argument list.

A procedure can return a value through the return command:

     return ?options? ?value?

which terminates the procedure returning value value, if specified, or just causes the procedure to return, if no value specified. (The ?options? part has to do with raising exceptions, which we will will not cover here.)

The return result of a user defined procedure is the return result of the last command executed by it.

So far we have seen the arguments of a procedure are passed using the call-by-value mechanism. They can be passed call by reference using the upvar command:

     upvar ?level? otherVar1 myVar1 ?otherVar2 myVar2 ...?

which makes accessible variables somewhere in a calling context with the current context. The optional argument level describes how many calling levels up to look for the variable. This is best shown with an example:

     set a 10
     set b 20
     
     proc add { first second } {
         upvar $first f $second s
         expr $f+$s
     }

which when called with

     add a b

will produce the result 30. If you use call-by-value instead:

     add $a $b

then the program will fail because when executing the procedure add it will take the first argument 10 as the level argument, a bad level. (Also variable 20 doesn't exist at any level.)

New control structures can be generated using the uplevel command:

     uplevel ?level? arg ?arg arg ...?

which is like eval, but it evaluates its arguments in a context higher up the calling stack. How far up the stack to go is given by the optional level argument.

     proc do { loop condition } {
         set nostop 1
     
         while { $nostop } {
             uplevel $loop
             if {[uplevel "expr $condition"] == 0} {
                 set nostop 0
              }
         }
     }

which when called with this

     set x 5
     do { puts $x; incr x -1 } { $x > 0 }

will print

     5
     4
     3
     2
     1

(Please note: this doesn't quite work for all kinds of calls because of break, continue, and return. It is possible to get around these problem, but that is outside the scope of this tutorial.)