Skip to content
VLSI Mentor

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc greet {name} {
    puts "hello, $name"
}

greet world
greet "Ada Lovelace"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
hello, world
hello, Ada Lovelace

Three 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set r [proc nothing {} {}]
puts "'[set r]'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
''

And your new command is indistinguishable from a built-in as far as the interpreter is concerned:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc double {n} { expr {$n * 2} }

puts [double 21]
puts [llength [info commands double]]
puts [llength [info commands llength]]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
42
1
1

info commands finds both. There is one namespace of commands, and proc adds to it.

3. The body is a script, so brace it

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set name "at definition time"
proc broken {name} "puts \"hello, $name\""
broken "at call time"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
hello, at definition time

The 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc implicit {} {
    set x 5
    expr {$x * 2}
}
puts [implicit]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
10

That includes commands you were not thinking of as producing a value. set returns what it assigned:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc lastIsSet {} {
    set y 7
}
puts [lastIsSet]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
7

And puts returns nothing, so a procedure ending in puts returns the empty string:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc lastIsPuts {} {
    puts -nonewline ""
}
puts "'[lastIsPuts]'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
''

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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc outer {} {
    return "outer saw: [inner]"
}

proc inner {} {
    return "inner"
}

puts [outer]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
outer saw: inner

outer 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc fact {n} {
    if {$n <= 1} { return 1 }
    return [expr {$n * [fact [expr {$n - 1}]]}]
}
puts [fact 5]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
120

6. Redefining is silent

Define the same name twice and the second wins, with no warning at all:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc dup {} { return "first" }
proc dup {} { return "second" }

puts [dup]
puts "definitions registered: [llength [info procs dup]]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
second
definitions registered: 1

There 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc scale {value factor} {
    expr {$value * $factor}
}

puts "args : [info args scale]"
puts "body : [string trim [info body scale]]"
puts "procs: [info procs sca*]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
args : value factor
body : expr {$value * $factor}
procs: scale

This 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 {}]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
3 entries, total 1
4 entries, total 10
0 entries, total 0

The 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?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc area {w h} {
    expr {$w * $h}
}
puts [area 3 4]
puts [area 10 10]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
12
100

3. 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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, and proc is an ordinary command that registers another command.
  • Your procedures are indistinguishable from built-ins to the interpreter; info commands lists 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 — including set's assigned value, and the empty string from puts or an untaken if.
  • 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 body and info procs let you inspect a procedure you cannot find the source of.

Where this fits

Part of the Tcl curriculum.