Tcl Part I · Module 10 · Procedures and Reuse
Default Arguments
A parameter can carry a value to use when the caller does not supply one, which lets a procedure grow new options without breaking every existing call. The syntax is one small change to the parameter list. The behaviour around it is worth more attention than the syntax, in two places: a default is stored as literal text rather than evaluated, which surprises anyone who tries to default a parameter to the current time; and Tcl will happily let you write a parameter list in which a default can never be used, without a word of complaint. Both are demonstrated here rather than asserted.
Foundation11 min readTclPart IProceduresDefaultsArguments
Part I · Module 10 · Chapter 10.3 · Default Arguments
1. The syntax
Replace a parameter name with a two-element list: the name and its default.
proc retry {command {attempts 3}} {
return "running $command, up to $attempts time(s)"
}
puts [retry build]
puts [retry build 5]running build, up to 3 time(s)
running build, up to 5 time(s)command is required; attempts is optional and falls back to 3.
2. It shows up in the usage line
From Chapter 10.2 §4, the generated message marks optional parameters with question marks:
retrywrong # args: should be "retry command ?attempts?"So a caller who gets it wrong is told immediately which arguments they may omit.
3. Several defaults
proc report {title {width 40} {border "="}} {
return "[string repeat $border $width]\n$title"
}
puts [report "One"]
puts [report "Two" 10]
puts [report "Three" 10 "-"]========================================
One
==========
Two
----------
ThreeThey fill from the left: supply the first optional parameter to reach the second. There is no way to skip one — Tcl matches by position only, so report "x" "" "-" would set width to the empty string rather than leaving it at 40.
4. Defaults can be any value
Including the empty string, and values containing spaces when grouped:
proc label {text {prefix ""} {suffix " <--"}} {
return "$prefix$text$suffix"
}
puts [label "hello"]
puts [label "hello" ">> "]
puts [label "hello" ">> " "!"]hello <--
>> hello <--
>> hello!An empty default is the usual way to say "optional, and absent by default" — then test it with eq "" as Chapter 8.7 described.
5. Tcl does not check that defaults come last
Here is the behaviour worth knowing, and it is not what most tutorials say.
You might expect Tcl to reject a parameter list where a required parameter follows an optional one. It does not:
proc weird {{a 1} b} {
return "a=$a b=$b"
}
puts "defined without complaint"defined without complaintNo error. Now try to use the default:
weird 9wrong # args: should be "weird ?a? b"That call failed. Arguments bind by position, so a single argument goes to a — and then b has nothing, which is an error. The only way to satisfy b is to supply both:
puts [weird 9 8]a=9 b=8So a's default can never be used. The parameter list is legal, the procedure works, and the default is dead code — while the usage line cheerfully advertises ?a? as optional.
6. A default is text, not an expression
The second surprise, and it arrives in two stages.
First, a bracketed default has to be grouped, because the parameter specifier is itself a two-element list. Written bare, it is not one:
proc stamped {msg {when [clock format 0 -format %Y]}} { }too many fields in argument specifier "when [clock format 0 -format %Y]"Tcl counted six words where it wanted two. Quote or brace it and the definition succeeds — which is where the real surprise is, because the default is then stored as a literal and used as-is. It is not evaluated when the procedure is called:
proc stamped {msg {when "[clock format 0 -format %Y]"}} {
return "$when: $msg"
}
puts [stamped "hello"][clock format 0 -format %Y]: helloThe brackets were never run. The default is the literal 31 characters, and that is what landed in when.
To compute a default, use an empty default and fill it in the body:
proc stamped2 {msg {when ""}} {
if {$when eq ""} {
set when [clock format 0 -format %Y]
}
return "$when: $msg"
}
puts [stamped2 "hello"]
puts [stamped2 "hello" "1999"]1970: hello
1999: helloThis is the standard shape for any default that has to be worked out rather than written down — a timestamp, a computed path, a value read from configuration.
7. Defaults and args together
args (Chapter 10.4) can follow defaulted parameters:
proc both {a {b 2} args} {
return "a=$a b=$b args='$args'"
}
puts [both 1]
puts [both 1 9]
puts [both 1 9 x y]a=1 b=2 args=''
a=1 b=9 args=''
a=1 b=9 args='x y'The binding order is visible across the three calls: positional arguments fill a first, then b, and only what is left over goes to args. A second argument can never reach args while b is still unfilled.
8. A worked example
Growing a procedure without breaking its callers:
# version 1 — one argument
proc summarise {rows} {
return "[expr {[llength $rows] / 2}] entries"
}
puts [summarise {a 1 b 2}]
# version 2 — a new option, every old call still works
proc summarise {rows {prefix "summary: "} {empty "nothing to report"}} {
if {[llength $rows] == 0} { return $empty }
return "$prefix[expr {[llength $rows] / 2}] entries"
}
puts [summarise {a 1 b 2}]
puts [summarise {a 1 b 2} ">> "]
puts [summarise {}]
puts [summarise {} ">> " "(empty)"]2 entries
summary: 2 entries
>> 2 entries
nothing to report
(empty)The original call still works unchanged, which is the whole point of defaults: they let an interface grow without a flag day.
9. Exercises
Exercise 1. Write greet taking a name and an optional greeting defaulting to hello.
Exercise 2. What usage line does proc f {a {b 1} {c 2}} produce when called with no arguments?
Exercise 3. Why can a's default never be used in proc weird {{a 1} b}, and does Tcl warn you?
Exercise 4. One of these fails to define and the other prints something unexpected. Say which is which, and what the author intended by both.
proc f1 {{n [expr 2+2]}} { return $n }
proc f2 {{n "[expr 2+2]"}} { return $n }Exercise 5. Rewrite the procedure from Exercise 4 so the default is actually computed.
Exercise 6. A procedure has four optional parameters and callers keep writing f $x {} {} "value". What does that indicate, and what would you change?
Answers
1.
proc greet {name {greeting hello}} {
return "$greeting, $name"
}
puts [greet world]
puts [greet world "good morning"]hello, world
good morning, world2. wrong # args: should be "f a ?b? ?c?" — one required parameter and two optional ones.
3. Because arguments bind by position: one argument fills a, leaving b unsatisfied, so any call that omits a fails. The only valid calls supply both, and then a's default is never consulted. Tcl does not warn — it accepts the definition silently and the usage line even shows ?a? as though it were optional.
4. f1 does not define at all — the specifier n [expr 2+2] is four words, not two:
too many fields in argument specifier "n [expr 2+2]"f2 defines, because the quotes make the default a single element, and then prints the literal ten characters [expr 2+2]. The author intended 4 in both cases. Grouping fixes the list, and nothing fixes the evaluation — defaults are never evaluated.
5.
proc f {{n ""}} {
if {$n eq ""} { set n [expr {2 + 2}] }
return $n
}
puts [f]
puts [f 9]4
96. It indicates the parameters are being used as named options, which positional arguments cannot express — the empty strings are placeholders to reach a later one. Replace them with a single options dictionary the caller fills in by name, or reorder so the most-overridden parameter comes first.
10. Remember
{name default}in the parameter list makes a parameter optional.- Optional parameters show as
?name?in the generated usage line. - They fill from the left — there is no way to skip one, so order them by how often they are overridden.
- Tcl does not check that defaults come last. A required parameter after an optional one is accepted silently and makes the default unreachable, while the usage line still claims it is optional.
- A default is stored as literal text and never evaluated —
{when [clock seconds]}stores the brackets, not the time. - To compute a default, use
""and fill it in the body with aneq ""test. argsmay follow defaulted parameters; positional arguments fill the named parameters first and the rest go toargs.- Defaults let an interface grow without breaking existing callers.
Where this fits
Part of the Tcl curriculum.
