Intermediate

Functions in Tcl

Learn how to define and call procedures in Tcl - parameters, default values, variable arguments, scope, recursion, and first-class higher-order functions with Docker-ready examples

In Tcl, the unit of reusable code is the procedure, defined with the proc command. True to Tcl’s philosophy that everything is a command, proc doesn’t introduce special “function” syntax—it is itself a command that registers a brand-new command in the interpreter. Once defined, your procedure is called exactly like any built-in command such as puts or set.

Because Tcl is a dynamic, string-based language, procedures are flexible: parameters have no declared types, you can supply default values, and a special args parameter lets a procedure accept any number of arguments. Variables inside a procedure are local by default, which keeps procedures self-contained—but Tcl gives you global and upvar to reach beyond that boundary when you need to.

This tutorial covers defining and calling procedures, passing parameters and returning values, default and variadic arguments, variable scope, recursion, and Tcl’s support for higher-order functions through apply and anonymous lambdas. Since Tcl is multi-paradigm (procedural and functional), we’ll lean into both styles.

Defining and Calling Procedures

A procedure is defined with proc name {parameters} {body}. The result of the procedure is whatever you return—or, if you omit return, the result of the last command in the body.

Create a file named functions.tcl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Functions (procedures) in Tcl

# A simple procedure with two parameters
proc add {a b} {
    return [expr {$a + $b}]
}

# Call it just like any built-in command
puts "add 3 4 = [add 3 4]"

# A procedure that returns a string
proc greet {name} {
    return "Hello, $name!"
}
puts [greet "Tcl"]

# An explicit return is optional - the last command's
# result automatically becomes the return value
proc square {x} {
    expr {$x * $x}
}
puts "square 5 = [square 5]"

The procedure body runs when the command is called, and [add 3 4] uses command substitution to capture the returned value. Note that expr {...} is used for arithmetic—in Tcl, math is not built into the language but performed by the expr command.

Default and Variable Arguments

Tcl supports default parameter values by writing a parameter as a {name value} pair. The special args parameter collects all remaining arguments into a list, giving you variadic procedures.

Create a file named functions_args.tcl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# Default parameters and variable arguments

# A default value is supplied as a {name value} pair.
# If "exp" is omitted, it defaults to 2.
proc power {base {exp 2}} {
    return [expr {$base ** $exp}]
}
puts "power 5    = [power 5]"
puts "power 2 10 = [power 2 10]"

# The special "args" parameter collects every remaining
# argument into a list of any length.
proc sum {args} {
    set total 0
    foreach n $args {
        set total [expr {$total + $n}]
    }
    return $total
}
puts "sum 1 2 3 4   = [sum 1 2 3 4]"
puts "sum (no args) = [sum]"

The ** operator is Tcl’s exponentiation operator. Because exp has a default of 2, calling power 5 squares the base, while power 2 10 overrides the default.

Variable Scope

Variables created inside a procedure are local—they vanish when the procedure returns and never touch the caller’s variables. To work with a global variable, declare it with global. To modify a caller’s variable by reference, use upvar.

Create a file named functions_scope.tcl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# Variable scope in Tcl

set counter 0   ;# a global variable

# By default, assignments inside a proc are LOCAL.
proc tryLocal {} {
    set counter 99
    return $counter
}
puts "inside proc:  [tryLocal]"
puts "global still: $counter"

# "global" links the name to the global variable.
proc increment {} {
    global counter
    incr counter
}
increment
increment
puts "after two increments: $counter"

# "upvar" links a caller's variable to a local name,
# giving you pass-by-reference semantics.
proc double {varName} {
    upvar 1 $varName x
    set x [expr {$x * 2}]
}
set value 21
double value
puts "doubled value: $value"

Notice that double receives the name value (not its contents), and upvar 1 binds the local x to the caller’s variable one level up the call stack. Mutating x therefore changes value in place—this is how Tcl procedures modify their arguments.

Recursion

A procedure can call itself, making recursion natural in Tcl. Here are the classic factorial and Fibonacci examples.

Create a file named functions_recursion.tcl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Recursion in Tcl

# Classic factorial
proc factorial {n} {
    if {$n <= 1} {
        return 1
    }
    return [expr {$n * [factorial [expr {$n - 1}]]}]
}
puts "factorial 5  = [factorial 5]"
puts "factorial 10 = [factorial 10]"

# Recursive Fibonacci
proc fib {n} {
    if {$n < 2} {
        return $n
    }
    return [expr {[fib [expr {$n - 1}]] + [fib [expr {$n - 2}]]}]
}

# Build a list of the first 10 Fibonacci numbers
set fibs {}
for {set i 0} {$i < 10} {incr i} {
    lappend fibs [fib $i]
}
puts "first 10 fib: $fibs"

Each recursive call gets its own local n, so the call stack keeps the intermediate values separate. The nested [expr {...}] calls evaluate the arguments before the outer multiplication or addition runs.

Higher-Order Functions

Tcl can treat procedures as data. You can pass a procedure’s name and invoke it indirectly, or create anonymous functions—lambdas written as a {args body} list—and run them with the apply command.

Create a file named functions_higher_order.tcl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Higher-order functions in Tcl

# Pass a procedure's NAME and call it indirectly with $fn.
proc applyTwice {fn x} {
    return [$fn [$fn $x]]
}
proc inc {n} { return [expr {$n + 1}] }
puts "applyTwice inc 5 = [applyTwice inc 5]"

# Anonymous functions: a {params body} list run with "apply".
set sq {x {expr {$x * $x}}}
puts "apply lambda 7  = [apply $sq 7]"

# A generic "map" that applies a lambda to every list element.
proc map {lambda list} {
    set result {}
    foreach item $list {
        lappend result [apply $lambda $item]
    }
    return $result
}
puts "map square: [map {x {expr {$x * $x}}} {1 2 3 4 5}]"

Because everything in Tcl is a string, inc is just the name of a command that can be stored in a variable and invoked via $fn. The apply command takes a lambda—a two-element list of parameters and body—and runs it on the spot, no name required.

Running with Docker

The efrecon/tcl image’s entrypoint is tclsh, so you pass the script path directly to the container.

1
2
3
4
5
6
7
8
9
# Pull the official Tcl image
docker pull efrecon/tcl:latest

# Run each example
docker run --rm -v $(pwd):/app -w /app efrecon/tcl:latest /app/functions.tcl
docker run --rm -v $(pwd):/app -w /app efrecon/tcl:latest /app/functions_args.tcl
docker run --rm -v $(pwd):/app -w /app efrecon/tcl:latest /app/functions_scope.tcl
docker run --rm -v $(pwd):/app -w /app efrecon/tcl:latest /app/functions_recursion.tcl
docker run --rm -v $(pwd):/app -w /app efrecon/tcl:latest /app/functions_higher_order.tcl

If you have Tcl installed locally, run any example with tclsh functions.tcl.

Expected Output

Running functions.tcl:

add 3 4 = 7
Hello, Tcl!
square 5 = 25

Running functions_args.tcl:

power 5    = 25
power 2 10 = 1024
sum 1 2 3 4   = 10
sum (no args) = 0

Running functions_scope.tcl:

inside proc:  99
global still: 0
after two increments: 2
doubled value: 42

Running functions_recursion.tcl:

factorial 5  = 120
factorial 10 = 3628800
first 10 fib: 0 1 1 2 3 5 8 13 21 34

Running functions_higher_order.tcl:

applyTwice inc 5 = 7
apply lambda 7  = 49
map square: 1 4 9 16 25

Key Concepts

  • proc defines a command — A procedure is just a new command registered in the interpreter; you call it exactly like puts or any built-in, with no special call syntax.
  • Implicit return — If you don’t use return, the value of the body’s last command becomes the procedure’s result.
  • Arithmetic lives in expr — Tcl has no built-in math operators in procedure bodies; wrap calculations in expr {...} (the braces protect the expression from premature substitution and improve performance).
  • Default and variadic parameters — Write {name value} for a default, and use the special args parameter to gather any number of trailing arguments into a list.
  • Local by default — Variables set inside a procedure are local; reach globals with global and modify a caller’s variables by reference with upvar.
  • Recursion is natural — Each call gets its own local frame, so self-referential procedures like factorial and fib work cleanly.
  • Procedures are first-class — Store a procedure’s name in a variable and invoke it via $fn, or write anonymous {params body} lambdas and run them with apply, enabling functional patterns like map.

Running Today

All examples can be run using Docker:

docker pull efrecon/tcl:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining