|
| 1 | +--- |
| 2 | +title: Functions, Records, and Sequences in TLA+ |
| 3 | +description: A comprehensive guide to functions in TLA+, including how to define and modify them, and understanding records and sequences as special cases of functions. |
| 4 | +--- |
| 5 | + |
| 6 | +## Functions, Records, and Sequences in TLA+ |
| 7 | + |
| 8 | +In TLA+, **functions** are fundamental data structures that map values from a **domain** to a **codomain** (also called range). If you come from a programming background, think of functions as **maps**, **dictionaries**, or **associative arrays**. |
| 9 | + |
| 10 | +> 🧠 Unlike operators, functions are *first-class values* in TLA+—they can be stored in variables, passed as arguments, and quantified over. |
| 11 | +
|
| 12 | +--- |
| 13 | + |
| 14 | +### 🔧 Defining Functions |
| 15 | + |
| 16 | +A function is defined using the **mapsto** syntax: `[x \in S |-> expr]` |
| 17 | + |
| 18 | +```tla |
| 19 | +\* A function that doubles each natural number |
| 20 | +double == [n \in Nat |-> 2 * n] |
| 21 | +
|
| 22 | +\* A function mapping people to their ages |
| 23 | +ages == [p \in {"Alice", "Bob", "Carol"} |-> |
| 24 | + IF p = "Alice" THEN 30 |
| 25 | + ELSE IF p = "Bob" THEN 25 |
| 26 | + ELSE 28] |
| 27 | +``` |
| 28 | + |
| 29 | +**Accessing function values** uses square bracket notation: |
| 30 | + |
| 31 | +```tla |
| 32 | +double[5] \* Evaluates to 10 |
| 33 | +ages["Alice"] \* Evaluates to 30 |
| 34 | +``` |
| 35 | + |
| 36 | +You can retrieve a function's domain with `DOMAIN`: |
| 37 | + |
| 38 | +```tla |
| 39 | +DOMAIN ages \* Evaluates to {"Alice", "Bob", "Carol"} |
| 40 | +``` |
| 41 | + |
| 42 | +--- |
| 43 | + |
| 44 | +### 📐 Set of All Functions: `[A -> B]` |
| 45 | + |
| 46 | +TLA+ provides syntax to define the **set of all functions** from domain `A` to codomain `B`: |
| 47 | + |
| 48 | +```tla |
| 49 | +[A -> B] |
| 50 | +``` |
| 51 | + |
| 52 | +This is the set of *all possible* functions that map every element of `A` to some element of `B`. |
| 53 | + |
| 54 | +#### Example |
| 55 | + |
| 56 | +```tla |
| 57 | +\* All functions from {1, 2} to {TRUE, FALSE} |
| 58 | +AllMappings == [{1, 2} -> {TRUE, FALSE}] |
| 59 | +
|
| 60 | +\* This set contains 4 functions: |
| 61 | +\* [1 |-> TRUE, 2 |-> TRUE ] |
| 62 | +\* [1 |-> TRUE, 2 |-> FALSE] |
| 63 | +\* [1 |-> FALSE, 2 |-> TRUE ] |
| 64 | +\* [1 |-> FALSE, 2 |-> FALSE] |
| 65 | +``` |
| 66 | + |
| 67 | +> 💡 The size of `[A -> B]` is `|B|^|A|` (cardinality of B raised to cardinality of A). |
| 68 | +
|
| 69 | +--- |
| 70 | + |
| 71 | +### ✏️ Modifying Functions with `EXCEPT` |
| 72 | + |
| 73 | +Since TLA+ is mathematical, you don't "mutate" a function. Instead, you create a **new function** based on an existing one using `EXCEPT`. |
| 74 | + |
| 75 | +```tla |
| 76 | +\* Original function |
| 77 | +ages == [p \in {"Alice", "Bob"} |-> |
| 78 | + IF p = "Alice" THEN 30 ELSE 25] |
| 79 | +
|
| 80 | +\* New function with Bob's age updated |
| 81 | +ages2 == [ages EXCEPT !["Bob"] = 26] |
| 82 | +``` |
| 83 | + |
| 84 | +After this, `ages2["Bob"]` evaluates to `26`, while `ages["Bob"]` remains `25`. |
| 85 | + |
| 86 | +#### Using `@` to Reference the Old Value |
| 87 | + |
| 88 | +The special symbol `@` refers to the **previous value** at that position: |
| 89 | + |
| 90 | +```tla |
| 91 | +\* Increment Alice's age by 1 |
| 92 | +ages3 == [ages EXCEPT !["Alice"] = @ + 1] |
| 93 | +\* ages3["Alice"] is now 31 |
| 94 | +``` |
| 95 | + |
| 96 | +#### Multiple Updates |
| 97 | + |
| 98 | +You can update multiple keys in a single `EXCEPT`: |
| 99 | + |
| 100 | +```tla |
| 101 | +ages4 == [ages EXCEPT !["Alice"] = 31, !["Bob"] = 26] |
| 102 | +``` |
| 103 | + |
| 104 | +#### Nested Function Updates |
| 105 | + |
| 106 | +For functions of functions (nested structures), use chained `!` notation: |
| 107 | + |
| 108 | +```tla |
| 109 | +\* A nested structure: departments -> employees -> salaries |
| 110 | +company == [dept \in {"Engineering", "Sales"} |-> |
| 111 | + [emp \in {"Alice", "Bob"} |-> 50000]] |
| 112 | +
|
| 113 | +\* Give Alice in Engineering a raise |
| 114 | +company2 == [company EXCEPT !["Engineering"]["Alice"] = 75000] |
| 115 | +``` |
| 116 | + |
| 117 | +> ⚠️ **Performance Note:** TLA+ is mathematics, not a programming language. Using `EXCEPT` does not "efficiently update" a function—it defines an entirely new function. There is no computational advantage to using `EXCEPT` over defining a fresh function. However, `EXCEPT` is often *clearer* and more concise. |
| 118 | +
|
| 119 | +--- |
| 120 | + |
| 121 | +### 🔄 Recursive Functions |
| 122 | + |
| 123 | +TLA+ allows defining functions that refer to themselves using the **recursive function definition** syntax: |
| 124 | + |
| 125 | +```tla |
| 126 | +f[x \in S] == expr \* where expr may reference f |
| 127 | +``` |
| 128 | + |
| 129 | +This is distinct from recursive *operators* (which use the `RECURSIVE` keyword). A recursive function definition creates a function value that can reference itself. |
| 130 | + |
| 131 | +#### Example: Factorial |
| 132 | + |
| 133 | +```tla |
| 134 | +factorial[n \in Nat] == IF n = 0 THEN 1 ELSE n * factorial[n - 1] |
| 135 | +``` |
| 136 | + |
| 137 | +Here, `factorial` is a function from `Nat` to `Nat` that refers to itself in its definition. |
| 138 | + |
| 139 | +#### Example: Fibonacci |
| 140 | + |
| 141 | +```tla |
| 142 | +fib[n \in Nat] == IF n <= 1 THEN n ELSE fib[n - 1] + fib[n - 2] |
| 143 | +``` |
| 144 | + |
| 145 | +#### Example: Sum of a Sequence |
| 146 | + |
| 147 | +```tla |
| 148 | +EXTENDS Sequences, Naturals |
| 149 | +
|
| 150 | +\* Sum elements of a sequence (using indices) |
| 151 | +sum[s \in Seq(Nat)] == |
| 152 | + IF s = <<>> THEN 0 |
| 153 | + ELSE Head(s) + sum[Tail(s)] |
| 154 | +
|
| 155 | +\* sum[<<1, 2, 3>>] evaluates to 6 |
| 156 | +``` |
| 157 | + |
| 158 | +#### Multiple Parameters |
| 159 | + |
| 160 | +For functions with multiple parameters, use a tuple or record as the domain: |
| 161 | + |
| 162 | +```tla |
| 163 | +\* Power function using a tuple domain |
| 164 | +power[pair \in Nat \X Nat] == |
| 165 | + LET base == pair[1] |
| 166 | + exp == pair[2] |
| 167 | + IN IF exp = 0 THEN 1 ELSE base * power[<<base, exp - 1>>] |
| 168 | +
|
| 169 | +\* power[<<2, 3>>] evaluates to 8 |
| 170 | +``` |
| 171 | + |
| 172 | +> 💡 **Tip:** Many recursive patterns over sequences and sets can be expressed more elegantly using operators from the `Folds` community module, such as `FoldSeq` and `FoldSet`. |
| 173 | +
|
| 174 | +--- |
| 175 | + |
| 176 | +### 📜 Sequences: Functions with Natural Number Domains |
| 177 | + |
| 178 | +**Sequences** are functions whose domain is `1..n` (the set `{1, 2, ..., n}`) for some natural number `n`. They represent ordered lists. |
| 179 | + |
| 180 | +#### Defining Sequences |
| 181 | + |
| 182 | +```tla |
| 183 | +EXTENDS Sequences |
| 184 | +
|
| 185 | +\* A sequence of three elements |
| 186 | +colors == <<"red", "green", "blue">> |
| 187 | +
|
| 188 | +\* Equivalent to: |
| 189 | +colors == [i \in 1..3 |-> |
| 190 | + IF i = 1 THEN "red" |
| 191 | + ELSE IF i = 2 THEN "green" |
| 192 | + ELSE "blue"] |
| 193 | +``` |
| 194 | + |
| 195 | +#### Accessing Elements |
| 196 | + |
| 197 | +```tla |
| 198 | +colors[1] \* Evaluates to "red" |
| 199 | +colors[2] \* Evaluates to "green" |
| 200 | +Len(colors) \* Evaluates to 3 |
| 201 | +DOMAIN colors \* Evaluates to {1, 2, 3} |
| 202 | +``` |
| 203 | + |
| 204 | +> ⚠️ Sequences are **1-indexed**, not 0-indexed! |
| 205 | +
|
| 206 | +#### Set of All Sequences |
| 207 | + |
| 208 | +```tla |
| 209 | +\* All sequences of elements from set S |
| 210 | +Seq(S) |
| 211 | +
|
| 212 | +\* Example: all sequences of bits |
| 213 | +Seq({0, 1}) \* Contains <<>>, <<0>>, <<1>>, <<0,0>>, <<0,1>>, ... |
| 214 | +``` |
| 215 | + |
| 216 | +> Note: `Seq(S)` is an *infinite* set (for non-empty `S`), so TLC cannot enumerate it directly. Use bounded versions like `BoundedSeq(S, n)` from SequencesExt. |
| 217 | +
|
| 218 | +--- |
| 219 | + |
| 220 | +### 📦 Records: Functions with String Domains |
| 221 | + |
| 222 | +**Records** are simply functions whose domain is a set of strings (field names). TLA+ provides convenient syntax for working with them. |
| 223 | + |
| 224 | +#### Defining Records |
| 225 | + |
| 226 | +```tla |
| 227 | +\* A record with fields "name" and "age" |
| 228 | +person == [name |-> "Alice", age |-> 30] |
| 229 | +
|
| 230 | +\* Equivalent to: |
| 231 | +person == [f \in {"name", "age"} |-> |
| 232 | + IF f = "name" THEN "Alice" ELSE 30] |
| 233 | +``` |
| 234 | + |
| 235 | +#### Accessing Fields |
| 236 | + |
| 237 | +Use dot notation or bracket notation: |
| 238 | + |
| 239 | +```tla |
| 240 | +person.name \* Evaluates to "Alice" |
| 241 | +person["name"] \* Also evaluates to "Alice" |
| 242 | +``` |
| 243 | + |
| 244 | +#### Set of All Records |
| 245 | + |
| 246 | +```tla |
| 247 | +\* All possible person records |
| 248 | +People == [name: {"Alice", "Bob"}, age: {25, 30, 35}] |
| 249 | +
|
| 250 | +\* This is the set of all records with: |
| 251 | +\* - "name" field from {"Alice", "Bob"} |
| 252 | +\* - "age" field from {25, 30, 35} |
| 253 | +\* Contains 2 * 3 = 6 records |
| 254 | +``` |
| 255 | + |
| 256 | +> 🔍 Note the syntax difference: `:` for record sets, `|->` for specific records. |
| 257 | +
|
| 258 | +#### Updating Records with `EXCEPT` |
| 259 | + |
| 260 | +```tla |
| 261 | +person2 == [person EXCEPT !.age = 31] |
| 262 | +\* Or equivalently: |
| 263 | +person2 == [person EXCEPT !["age"] = 31] |
| 264 | +``` |
| 265 | + |
| 266 | +--- |
| 267 | + |
| 268 | +### 🧰 Useful Modules |
| 269 | + |
| 270 | +TLA+ provides operations for functions and sequences across several modules: |
| 271 | + |
| 272 | +| Module | Description | |
| 273 | +|--------|-------------| |
| 274 | +| `Sequences` | Standard module with basic sequence operations (`Len`, `Head`, `Tail`, `Append`, `SubSeq`, `\o`) | |
| 275 | +| `Functions` | Community module with function utilities (`Range`, `Restrict`, `Inverse`, injections, surjections, bijections) | |
| 276 | +| `SequencesExt` | Community module with extended sequence operations (`ToSet`, `SetToSeq`, `Reverse`, `FlattenSeq`, `BoundedSeq`) | |
| 277 | +| `Folds` | Community module with fold operations over sequences and sets (`FoldSeq`, `FoldSet`) | |
| 278 | + |
| 279 | +#### How to Explore Module Contents |
| 280 | + |
| 281 | +**In VS Code / Cursor:** Add an `EXTENDS` statement and Ctrl+Click (Cmd+Click on macOS) on the module name to navigate to its source: |
| 282 | + |
| 283 | +```tla |
| 284 | +EXTENDS Sequences, Functions, SequencesExt |
| 285 | +``` |
| 286 | + |
| 287 | +**On the web:** Browse the standard modules at [github.com/tlaplus/tlaplus](https://github.com/tlaplus/tlaplus/tree/master/tlatools/org.lamport.tlatools/src/tla2sany/StandardModules) and CommunityModules at [github.com/tlaplus/CommunityModules](https://github.com/tlaplus/CommunityModules/tree/main/modules). |
| 288 | + |
| 289 | +**For AI assistants:** Use the TLA+ MCP tools `tlaplus_mcp_sany_modules` to list available modules, and `tlaplus_mcp_sany_symbol` to extract symbols from a specific module. |
| 290 | + |
| 291 | +--- |
| 292 | + |
| 293 | +### 🧠 Summary |
| 294 | + |
| 295 | +| Concept | Domain | Syntax | Access | |
| 296 | +|---------|--------|--------|--------| |
| 297 | +| **Function** | Any set | `[x \in S \|-> expr]` | `f[x]` | |
| 298 | +| **Sequence** | `1..n` | `<<e1, e2, ...>>` | `s[i]` (1-indexed) | |
| 299 | +| **Record** | Strings | `[field1 \|-> v1, ...]` | `r.field` or `r["field"]` | |
| 300 | + |
| 301 | +| Set of... | Syntax | |
| 302 | +|-----------|--------| |
| 303 | +| All functions A → B | `[A -> B]` | |
| 304 | +| All sequences | `Seq(S)` | |
| 305 | +| All records | `[field1: S1, field2: S2, ...]` | |
| 306 | + |
| 307 | +> 🎯 **Key Insight:** Records and sequences are not special types—they're just functions with specific domains. This unification is a hallmark of TLA+'s elegant mathematical foundation. |
0 commit comments