Skip to content
VLSI Mentor

Tcl Part I · Module 10 · Procedures and Reuse

Arguments, and What Happens When the Count Is Wrong

Declaring parameters is one line of syntax and takes about a minute to learn. What is worth more of your time is the error you get when a call does not match — because Tcl does not merely complain, it prints a usage line that it generated from your own parameter list. That line is never out of date, it uses a notation that distinguishes required arguments from optional ones, and it names the procedure, which tells you immediately whether the mistake is at the call site or in the definition. Learning to read it turns the commonest error in Tcl from a nuisance into the fastest diagnostic you have.

Foundation10 min readTclPart IProceduresArgumentsErrors

Part I · Module 10 · Chapter 10.2 · Arguments, and What Happens When the Count Is Wrong

1. Declaring parameters

The parameter list is a Tcl list of names:

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

puts [scale 3 4]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
12

Inside the body, each parameter is an ordinary local variable holding the corresponding argument. Arguments are matched by position; there is nothing in Tcl like passing by keyword.

A procedure with no parameters takes an empty list:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc now {} {
    return "no arguments needed"
}
puts [now]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
no arguments needed

2. The count must match

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

scale 3
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
wrong # args: should be "scale value factor"

Too many gives the same message:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
scale 3 4 5
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
wrong # args: should be "scale value factor"

That is deliberate. Both are the same mistake — the call does not match the contract — and the message answers the only question you have, which is what the contract actually is.

3. The usage line is generated

This is the part worth internalising. Tcl did not find that message in your source; it built it from the parameter list at the moment of failure. So it is always accurate, even for a procedure someone edited five minutes ago and even for one you cannot find.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc connect {host port user} { }
connect localhost
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
wrong # args: should be "connect host port user"

You now know the name, the number of arguments and, because you chose the parameter names, roughly what each one is for — without opening a file.

4. The notation distinguishes optional arguments

A parameter with a default — covered properly in Chapter 10.3 — appears in question marks:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc retry {command {attempts 3}} {
    return "$command x$attempts"
}

puts [retry cmd]
puts [retry cmd 5]

retry
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cmd x3
cmd x5
wrong # args: should be "retry command ?attempts?"

command is required and attempts is optional. That is the same ?...? convention the built-in commands use — puts ?-nonewline? ?channelId? string from Chapter 1.7 — because it is generated by the same mechanism.

And args, which collects any number of trailing arguments (Chapter 10.4), shows as a literal:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc logmsg {level args} {
    return "\[$level\] [join $args " "]"
}

puts [logmsg WARN two words]
logmsg
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
[WARN] two words
wrong # args: should be "logmsg level ..."

The trailing ... means "and any number more".

Parameter listUsage line
{value factor}scale value factor
{command {attempts 3}}retry command ?attempts?
{level args}logmsg level ...

Reading the message therefore tells you the full calling convention, not just that you got it wrong.

5. The check happens at call time

Tcl does not validate calls when it reads your script. A wrong call sitting in a branch that never runs is never reported:

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

proc never_called {} {
    scale 3          ;# wrong, and nothing notices
}

puts "script loaded fine"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
script loaded fine

This is the same late-binding behaviour as Chapter 10.1 §5: names and arities are resolved when a line runs. It is why a rarely-taken error path is exactly where a bad call survives longest, and an argument for exercising those paths deliberately.

6. Arguments arrive as values

Each argument is a value, copied in the sense Chapter 6.6 described — modifying a parameter inside a procedure cannot affect the caller:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc bump {n} {
    incr n
    return $n
}

set count 10
puts "returned : [bump $count]"
puts "original : $count"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
returned : 11
original : 10

A value containing spaces arrives as one argument provided the caller grouped it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc show {a b} { return "a='$a' b='$b'" }

puts [show "two words" x]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
a='two words' b='x'

Without the quotes it would be three words and a wrong # args. The grouping rules from Chapter 2.7 apply to procedure calls exactly as they do everywhere else — a procedure call is just a command.

7. A worked example

A procedure whose usage line documents it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc report {title rows {width 40}} {
    puts [string repeat "=" $width]
    puts $title
    puts [string repeat "-" $width]
    foreach {name value} $rows {
        puts [format "  %-20s %s" $name $value]
    }
}

report "Run summary" {cycles 12000 errors 0} 32

report "Run summary"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
================================
Run summary
--------------------------------
  cycles               12000
  errors               0
wrong # args: should be "report title rows ?width?"

The failing call tells the next person everything: two required arguments, one optional, and what each is called.

8. Exercises

Exercise 1. What message does scale 3 produce for proc scale {value factor}, and what does scale 3 4 5 produce?

Exercise 2. Write clamp taking a value, a minimum and a maximum, returning the value limited to that range.

Exercise 3. Given the usage line should be "connect host port ?timeout?", how many arguments are required and how many are accepted?

Exercise 4. Why is proc scale {value factor} better than proc scale {a b} even though they behave identically?

Exercise 5. This script loads without error and is wrong. Explain why nothing is reported.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc f {a b} { expr {$a + $b} }
proc g {} { f 1 }
puts "loaded"

Exercise 6. A call fails with wrong # args. Can any part of the procedure's body have run? Why does that matter?

Answers

1. Both produce wrong # args: should be "scale value factor". Too few and too many are the same mistake and get the same message.

2.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc clamp {value min max} {
    if {$value < $min} { return $min }
    if {$value > $max} { return $max }
    return $value
}
puts [clamp 5 0 10]
puts [clamp -3 0 10]
puts [clamp 99 0 10]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
5
0
10

3. Two are required (host and port) and three are accepted — timeout is optional, as the question marks show.

4. Because the parameter names appear in the generated usage line, so they are part of every error message the procedure will ever produce. should be "scale value factor" tells a caller what to pass; should be "scale a b" tells them only how many.

5. Tcl checks argument counts when a call executes, not when the script is read. g is never called, so f 1 is never reached and never validated. The error appears the first time something calls g — possibly much later, in a rarely-taken branch.

6. No. The arity check happens before the body begins, so a wrong # args guarantees nothing in the procedure ran. That matters because the failure cannot have left partial state behind — you can retry the corrected call without cleaning up.

9. Remember

  • The parameter list is a Tcl list of names, matched to arguments by position. There are no keyword arguments.
  • Too few and too many arguments give the same wrong # args message, because they are the same mistake.
  • The usage line is generated from the parameter list, so it is always accurate and never out of date.
  • ?name? marks an optional parameter and a trailing ... marks args — the same notation the built-ins use.
  • Parameter names are part of your error messages. Name them for the reader.
  • Arity is checked at call time, so a wrong call in an unexecuted branch is never reported.
  • Nothing in the body runs when the count is wrong, so the failure leaves no partial state.
  • Arguments are values — modifying a parameter cannot affect the caller.

Where this fits

Part of the Tcl curriculum.