TCLWISE
An introduction to the Tcl programming language
Sponsored Project: The Jim interpreter A small footprint implementation of Tcl |
Send a comment to the author
|
7. MORE ON PROCEDURES
Tcl procedures are not new to the reader, we already had the
chance to write some simple procedure using the proc command.
Still there are details that must be investigated in order to be
able to write non trivial programs. This chapter will explain
what is a local variable for a Tcl procedure, how to write procedures
with a variable number of arguments, with default arguments, how
to write recursive procedures, and finally what are and how to access
to global variables from Tcl procedures.
7.1 Local variables Tcl programs can create new variables using commands like set,
append, lappend, and so on. If a variable is created inside a
procedure, then the variable is called local variable, and his
visibility and life are closely related to the procedure call.
The following foobar procedure creates two variables called
a and b, and returns a two-element list with the value of
a as first element, and the value of b as second element:
Every time we call the procedure (that does not take any argument), it
will return the two elements list "foo bar". In order to understand
well what a local variable is, we'll try to follow what happens
inside Tcl every time the procedure foobar is called:
The first event is the execution of the command set a foo, this
command creates a local variable with the string "foo" as content.
The same happens when the second line of the foobar procedure
is executed, the local variable b is created. Finally the values
of the two variables are used as arguments of the list command.
Because list $a $b is the last command of the foobar procedure,
the Tcl interpreter is now ready to return its value to the caller:
the procedure can exit. What happens to the local variables a and b?
They are just destroyed, the value is lost, and this happens every time
the foobar procedure is called, for every call this two variables
are created and then destroyed.
So we can say: in Tcl local variables are variables created
inside a procedure (i.e. while a procedure is running), and
destroyed once the creating procedure is ready to return to the caller.
So far we know the life duration of a local variable, but what
about it's ability to be accessed? A local variable can be accessed
only by code inside the procedure that created it, in other terms
the visibility of a local variable is limited to the creating procedure
(actually this rule have an important exception, but you need to wait
the next chapter to know more).
7.2 Top level The previous section explained the behaviour of variables created
inside procedures, but actually in Tcl it's possible to run code
outside a procedure, in a context called top level.
For example, when you start the tclsh and write some Tcl command,
the command is executed at top level. In general every Tcl code
that does not appear inside a procedure is at top level. Look at
the following program as an example:
The program calls the puts command while at top level, then the
proc command, still at top level, and finally the foo command
is called. At this point we are inside the foo function (no longer
at top level).
Because toplevel is something like a procedure that Tcl automatically
call to start the program, and that never returns while the program
is running, variables created at top level are never destroyed.
This variables are called global variables.
7.3 Global variables Actually global variables are somewhat special, not only they are never
destroyed (unless the program explicitly destroy a global variable
via the unset command), but also they can be created and accessed
from Tcl procedures (i.e. it's possible to create or access global
variables while not at top level). In order to make it possible,
the global command is used. In the first example we will create
a global variable using set at top level, then we will access it
from a procedure using the global command:
The top level set command creates the global variable PI containing
the approximated value of PI. The area procedure uses the global
variable to compute the area of a circle having a given radius
(passed as unique argument to the area procedure). After the command
global PI is called, the area procedure is free to use the PI
variable.
A procedure may also create a global variable in a similar way:
As you can guess, the output of this program is "3.1415926536".
The createPI command creates the PI global variable, that can
be accessed directly from top level by the puts $PI command.
It's important to understand that while global variables are useful
in order to take some important state of the program, a wise use
is raccomended: procedures using global variables tend to be less
reusable in other contexts, to have subtle side effects, and in
general don't help a lot in the attempt of writing clean and readable code.
7.4 Procedures arguments and pass by value Arguments of procedures are a special kind of local variables.
The only thing that is special about this variables is that they
are created automatically every time the procedure is called,
and their value is set to the value of the corresponding argument
passed to the command. Look at the following code:
The procedure myproc takes one argument x, and set it to the empty
string. It's just a dummy function useful only for this example.
The code creates a variable list with a list of five numbers, then
calls myproc $list. What happens at this point? $list is expanded
to it's value, then the myproc procedure is called, so it's like
to call directly:
What this means is that we always pass strings as procedure arguments.
At this point the execution of myproc starts: the first thing it
does is to create a local variable x, that's the argument of the
function, setting "1 2 3 4 5" as value of this variable.
The first line of the myproc function will then set the empty
string as value of the x variable. Finally the puts $list command
prints the value of the list variable on the screen, so the program
will output "1 2 3 4 5".
What's the point here? That in Tcl, arguments to procedures are always
passed by value. This means that myproc can't alter the value
of the list variable: this value is just expanded, passed to
myproc, and then set to the x argument. Unless a procedure requires
the name of a variable as argument, and not a value, you are always
safe, the procedure should not alter the value of variables of the caller.
This makes Tcl programming very safe, you may not remeber at all how
mystrangeprocedure works internally, you know that the following
code fragment can't alter the value of the list stored in the l variable:
mystrangeprocedure can mess with its argument as much as it likes,
but still the value of the variable l will continue to be a two
element list "foo bar".
At this point you should wonder how it is possible in Tcl to write
a procedure like the incr command, able to increment a variable
living in the context of the caller procedure. In the next chapter
we will see how in Tcl rules are done to be violated thanks to the
great flexibility and introspection offered by this language.
7.5 Procedures with a variable number of arguments Remember our attempt to write a + procedure? This is the code
we wrote in a previous chapter:
This code is ok, but works with just two arguments. If I want
to sum three numbers, instead to write + 1 2 3 I've to write
+ 1 [+ 2 3]. I'm sure you don't like this limitation, and I agree.
In Tcl is very simple to write procedures with a variable number
of arguments, so we can write a new version of the + procedure
able to accept from 1 to infinite arguments. This is the code:
We can test it interactively using the tclsh as usually to make
sure it is working as expected:
Now let's see how it works. As you can see the proc command was
called with {x args} as second argument (that is the argument list
of the procedure we are creating). The second argument is args,
and it is a special one: if the last argument of the argument list
is exactly the string args, the function can accept an infinite
number of arguments, that are stores as a list into the args argument
when the procedure is called.
In the example of the + procedure, if we call it with a single argument
it will be assigned to x, and args will be an empty list.
If we call it with two arguments the first is assigned to x, and
the second as unique element of the list args, and so on.
The following is table of what x and args will contain with
a different number of arguments passed to +:
This is why the implementation of the + procedure uses foreach
in order to iterate over the list of arguments, adding all the
arguments to the first one (contained in x).
Note that as long as args appears as the last argument, any number
of normal arguments may be provided (including zero), for instance
{x y z args} is a valid argument list for a procedure that will require
from 3 to an infinite number of arguments. The first three arguments
will be assigned to x, y, z, all the remaining arguments will
be put in a list and assigned to args.
7.6 Procedures with default arguments Another useful tool of the proc command is the ability to write
procedures with default arguments. Default arguments can be
omitted when calling the procedure, and will default to a value
specified when the procedure was created. There are a lot of cases
when this is desiderable, we already shown core commands where
this feature is used (for example in the join command used
to join elements of a list into a string, the joinString argument
can be omitted and will default to a single space).
As an example we can write a procedure that increment every element
of a list by one:
This is some output:
Note that we need to initialize the result variable to the empty
list to be sure the function will work well if the input is an empty
list, otherwise the result variable may not be created because
lappend will never be called, and return $result will generate
an error.
What about if I want to increment the elements by 10 and not just
by 1? We can rewrite the function so that it will take an additional
argument called increment, and use its value inside the foreach
loop to create the new string.
Again this works well, cut&paste the code into the tclsh and
try yourself:
But immagine that after some time you use this procedure in your programs
you discover that 80% of the times you need to increment just by one,
why you can't just write lincr $mylist instead of lincr $mylist 1,
and specify the increment only when it is different than the common case?
This is where default arguments enter in scene:
The new version is exactly the same as the previous one, but for a difference
in the argument list of the procedure that now is {l {increment 1}}.
In short, if one of the arguments in the argument list is itself a two
elements list, the first is interpreted as the name of the argument
and the second as the default value to give to the argument if it is
not specified. Now the function can accept one or two arguments, with
just one argument it will increment the list elements by one, with more
it will increment using the specified vale:
A procedure may have multiple default arguments, but they must all
be at the end of the arguments list, you can't add a default argument
in the middle like {a {b 10} c}, but it's ok to have multiple
default arguments like in the case {a {b 10} {c 20}}: in the example
if you specify just one argument, b will default to 10 and c to 20,
if you specify one argument more it will be used to set the value
of b, finally if you add another one it will be used for c.
An exception to this rule is that it's possible to have the args
special argument to write a procedure with variable number of arguments
where some of the last arguments have a default value:
Just a final note, what about if the default value is inside a
variable or you want to compute it at run-time with command substitution?
If the argument list uses { } grouping, variable and command substitution
will not happen, so you need to write the procedure in a different
way using the list command, like this:
The second argument of proc is a list, so you can create it at
runtime. Default arguments and procedures with a variable number of
arguments are improtant because it's nice to write procedures that
require less typing for the base cases and can accept an infinite number
of arguments when it makes sense (like in the case of the + procedure).
7.7 Recursion A Tcl procedure can call itself, this makes possible to write recursive
procedures. Recursion is so important because many problems are trivial
to express in terms of a simple case of theirself.
The first example of recursive procedure is used to compute the maximum
element of a list of integers. We know how to solve the case of a list
of length one (the max is just the only element), and using recursion
we can solve the problem for any length of the list:
The procedure can be read: if the list length is one, the max
is the only element it contains, otherwise split the list in two
parts, the first element, and the rest of the list.
If the first element is greater than the max of the rest, it is
the max of the list, otherwise the max of the list is the max
of the rest of the list. The procedure will work with every non
empty list of integers:
Another example of recursive procedure is the classical Fibonacci
function, that's defined as:
The Tcl implementation of the Fibonacci function is the following:
Before to look at the procedure details, note that we put expr's
expression inside braces. You may wonder how it's possible that
command substitution will take effect if grouping will prevent
it: the trick is that expr command performs its own turn of
variables and command substitution to the argument we pass, and that
it is much faster if we provide an expression grouped with braces.
So instead to write expr $a+$b, you can also write expr {$a+$b},
letting the compiler to optimize the code to run faster.
About the fib procedure, all should be clear, it is a plain Tcl translation
of the mathematical definition of the Fibonacci function.
It is interesting to note that this function will end computing multiple
times itself. for example in order to compute FIB(5) the value of
FIB(2) is computed 3 times, this is not optimal because FIB(2) will
always have the same value, so it's useful to compute it more then
one time. We will see in the next chapters how it is possible to
write a Tcl procedure that makes possible to automatically cache
already computed values of recursive procedures. The technique of
caching the already computed values of a procedure is called
memoization, thanks to the introspection capabilities of Tcl we
will be able to write a memoize procedure, that used as first command
of a procedure will turn that procedure in a memoized one.
7.8 Recursion limit In order to trap an infinite recursion error before it is too late
the Tcl interpreter will generate an error if a given recursion
depth (the number of nested calls) is reached. For example
the following procedure will exit with an error when called.
infinite will call infinite forever.
This may create problem sometimes, you may need to write code that
performs a recursion with a depth not allowed by the default limit.
In order to change this use the following Tcl command:
where *$newlimit$ is the value of the new recursion limit.
If you want to check what's the current recursion limit, call
this command without the last argument:
Note that to enlarge this limit too much may not always result to
the ability to write recursive procedures with the desidered recursion
depth: Tcl is implemented in C, and the interpreter calls itself,
so what may overflow is the C stack itself. If this happens you will
see an operating system error similar to stack overflow, or segmentation
fault.
Sometimes it's possible to write procedures where the recursion
appears only just before the procedure will return, this is called
tail recursion. As we will see in an advanced chapter of this book
it is possible to write a version of proc that does what is called
tail recursion optimization, this makes it possible to write
recursive procedures running in constant space.
|
Other Tcl/Tk books Index
2.1 Anatomy of a command
2.2 Grouping
2.3 Program structure
2.4 Substitution of commands
2.5 Substitution of variables
2.6 More on interpolation
2.7 Comments
2.8 That's it
3.1 User defined procedures
3.2 The if command
4.1 Tcl list
4.2 The foreach command
4.3 The lrange command
4.4 The lappend command
4.5 The lset command
4.6 The lsort command
4.7 List values against variable names
5.1 The append command
5.2 The string command
5.3 string range
5.4 string index
5.5 string equal
5.6 string compare
5.7 string match
5.8 string map
5.9 string is
5.10 More string subcommands
5.11 Advanced string matching
6.1 Converting strings to lists
6.2 From strings to list of chars
6.3 Converting lists to strings
6.4 Manipulating strings as lists
7.1 Local variables
7.2 Top level
7.3 Global variables
7.4 Procedures arguments and pass by value
7.5 Procedures with a variable number of arguments
7.6 Procedures with default arguments
7.7 Recursion
7.8 Recursion limit
8.1 The switch command
8.2 The for command
8.3 break and continue
8.4 The lack of goto
9.1 Programs executing programs: the eval command
9.2 Breaking the rules with uplevel
9.3 Passing variable names to procedures
9.4 Mapping scripts to lists
9.5 The rename command
9.6 Expanding lists into arguments in Tcl 8.5
Additional 20 chapters in the printed version.
Related man pages
Links
Author HomeTclers Wiki |
Copyright © 2004 Salvatore Sanfilippo. All rights reserved. This online book is for personal use only. It cannot be copied to other web sites or further distributed in any form. |