Tcl Part I · Module 10 · Procedures and Reuse
proc: Defining Your Own Command
Everything you have written so far has been a sequence of commands at the top level of a script. This module is about packaging a working fragment so it has a name, takes inputs, and can be called from anywhere — and Tcl does that in a way worth noticing. In most languages a function definition is syntax, handled specially by the compiler. In Tcl, proc is an ordinary command whose job is to register another command. Once your procedure exists there is nothing second-class about it: the interpreter cannot tell it apart from a built-in, which is the fact that the rest of this module keeps cashing in.
Foundation11 min readTclPart IProceduresprocReuse
Part I · Module 10 · Chapter 10.1 · proc: Defining Your Own Command
1. Defining and calling
proc greet {name} {
puts "hello, $name"
}
greet world
greet "Ada Lovelace"hello, world
hello, Ada LovelaceThree arguments to proc: the name, the parameter list, and the body. After it runs, greet is a command. You call it the way you call puts or llength — name first, arguments after, no parentheses and no commas.
2. proc is a command
Count the words on that definition: proc, greet, {name}, and the body. Four words, the first being the command name — the same shape as every line since Chapter 2.1. The braces group the parameter list and the body into single words, exactly as Chapter 2.8 described.
It even returns a value, like any command — the empty string:
set r [proc nothing {} {}]
puts "'[set r]'"''And your new command is indistinguishable from a built-in as far as the interpreter is concerned:
proc double {n} { expr {$n * 2} }
puts [double 21]
puts [llength [info commands double]]
puts [llength [info commands llength]]42
1
1info commands finds both. There is one namespace of commands, and proc adds to it.
3. The body is a script, so brace it
proc greet {name} {
puts "hello, $name"
}The body is handed to proc unevaluated and stored. $name is not looked up when the procedure is defined — it is looked up each time the procedure runs, when a local called name exists.
Quote the body instead and that stops being true:
set name "at definition time"
proc broken {name} "puts \"hello, $name\""
broken "at call time"hello, at definition timeThe body was substituted once, while proc was being called, so the procedure permanently prints whatever name held at that moment. This is Chapter 8.2 §8's quoted-body hazard in its most damaging form — the mistake is made once and every future call is wrong.
Brace the body. Always.
4. The result is the last command's result
A procedure with no return gives back whatever its final command produced:
proc implicit {} {
set x 5
expr {$x * 2}
}
puts [implicit]10That includes commands you were not thinking of as producing a value. set returns what it assigned:
proc lastIsSet {} {
set y 7
}
puts [lastIsSet]7And puts returns nothing, so a procedure ending in puts returns the empty string:
proc lastIsPuts {} {
puts -nonewline ""
}
puts "'[lastIsPuts]'"''5. Procedures can call procedures
Including ones defined later, because a command name is resolved when the call runs, not when the body is stored:
proc outer {} {
return "outer saw: [inner]"
}
proc inner {} {
return "inner"
}
puts [outer]outer saw: innerouter was defined before inner existed and works anyway. The flip side is that a typo in a command name inside a procedure is not detected until that line actually runs — the behaviour from Chapter 1.8.
And a procedure can call itself:
proc fact {n} {
if {$n <= 1} { return 1 }
return [expr {$n * [fact [expr {$n - 1}]]}]
}
puts [fact 5]1206. Redefining is silent
Define the same name twice and the second wins, with no warning at all:
proc dup {} { return "first" }
proc dup {} { return "second" }
puts [dup]
puts "definitions registered: [llength [info procs dup]]"second
definitions registered: 1There is one command called dup and it is the second one. Nothing records that a first ever existed.
7. Inspecting a procedure
Because procedures are data to the interpreter, you can ask about them:
proc scale {value factor} {
expr {$value * $factor}
}
puts "args : [info args scale]"
puts "body : [string trim [info body scale]]"
puts "procs: [info procs sca*]"args : value factor
body : expr {$value * $factor}
procs: scaleThis is genuinely useful when debugging a script you did not write and cannot find the definition of — info body prints it.
8. A worked example
Taking a fragment from Chapter 9.9 and giving it a name:
proc summarise {rows} {
set n 0
set total 0
foreach {name value} $rows {
incr n
incr total $value
}
return "$n entries, total $total"
}
puts [summarise {clk 3 rst 0 din -2}]
puts [summarise {a 1 b 2 c 3 d 4}]
puts [summarise {}]3 entries, total 1
4 entries, total 10
0 entries, total 0The loop was already correct; wrapping it gained a name, reuse, and the ability to try it on three inputs in three lines.
9. Exercises
Exercise 1. What are proc's three arguments, and what does proc itself return?
Exercise 2. Write area taking a width and a height and returning their product. Call it twice.
Exercise 3. What does this print and why?
proc f {} { set x 1 ; set y 2 }
puts [f]Exercise 4. Explain why the body must be braced rather than quoted, and what goes wrong if it is not.
Exercise 5. What does endsWithIf -5 return in section 4, and what would you change so that every call returns something meaningful?
Exercise 6. Two source files both define proc check {}. What happens, and what will the symptom look like?
Answers
1. A name, a parameter list, and a body. proc returns the empty string.
2.
proc area {w h} {
expr {$w * $h}
}
puts [area 3 4]
puts [area 10 10]12
1003. It prints 2. There is no return, so the procedure's result is its last command's result — and set y 2 returns the value it assigned.
4. A braced body is stored unevaluated and its substitutions happen on each call, when the parameters exist. A quoted body is substituted once, as proc runs, so variables are frozen to whatever happened to be in scope at definition time and every subsequent call uses those stale values.
5. It returns the empty string, because the if was not taken and an untaken if produces nothing. Add an explicit return for the other case:
proc endsWithIf {n} {
if {$n > 0} { return "positive" }
return "non-positive"
}6. The second definition to load silently replaces the first — no error, no warning, and info procs check still reports one. The symptom is behaviour that depends on load order, which looks like a bug in check itself rather than in whoever loaded the files.
10. Remember
proc name {params} {body}— three arguments, andprocis an ordinary command that registers another command.- Your procedures are indistinguishable from built-ins to the interpreter;
info commandslists both. - Brace the body. A quoted body is substituted once at definition time and is permanently wrong.
- With no
return, a procedure's result is its last command's result — includingset's assigned value, and the empty string fromputsor an untakenif. - Procedures can call procedures defined later, because names resolve at call time — and typos are therefore not caught until that line runs.
- Redefining a procedure is completely silent. The last definition wins and nothing records the first.
info args,info bodyandinfo procslet you inspect a procedure you cannot find the source of.
Where this fits
Part of the Tcl curriculum.
