learning_tla/lamport_video/docs/lecture02.md
2025-02-08 18:44:33 +01:00

235 lines
7.1 KiB
Markdown

# State machines in TLA+
## Converting plain text to TLA+ notation
An advantage of using TLA+ notation instead of whatever language teams write in
is that it forces them to reason in a more abstract way.
For the example program, we need to describe:
- possible initial values of variables: `i = 0` and `pc = "start"`
(i = 0) ⋏ (pc = "start")
- the relation between their values in the current and next states
if the current value of pc is "start",
then next value of i is in {0, 1, ... 1000}
next value of pc is "middle"
else if current value of pc is "middle"
then next value of i equals current value of i + 1. /* It is in `{1, 2, ... 1001}`*/
next value of pc is "done"
else /* pc is "done" */ no next values
- let's get rid of "the current value of X", by notating it just "X"
if pc is "start",
then next value of i is in {0, 1, ... 1000}
next value of pc is "middle"
else if pc is "middle"
then next value of i equals i + 1. /* It is in `{1, 2, ... 1001}` $/
next value of pc is "done"
else /* pc is "done" */ no next values
- let's get rid of "next value of X", by notating it just "X'"
if pc is "start",
then i' is in {0, 1, ... 1000}
pc' is "middle"
else if pc is "middle"
then i' equals i + 1. /* It is in `{1, 2, ... 1001}` */
pc' is "done"
else /* pc is "done" */ no next values
- let's replace "equals" by "=", "is in" by "∈" and drop the comments
if pc = "start"
then i' ∈ {0, 1, ... 1000}
pc' = "middle"
else if pc = "middle"
then i' = i + 1
pc' = "done"
else no next values
- TLA+ notates integer ranges as `low..high` with both limits included
if pc = "start"
then i' ∈ 0..1000
pc' = "middle"
else if pc = "middle"
then i' = i + 1
pc' = "done"
else no next values
- The "if/then" contain an implicit "and"
if pc = "start"
then i' ∈ 0..1000 ⋏ pc' = "middle"
else if pc = "middle"
then i' = i + 1 ⋏ pc' = "done"
else no next values
- Important point:
- these are not instructions for computing, but a formula
relating the values of `i`, `pc`, `i'`, and `pc'`.
- this does NOT mean `if pc == "start" then do the then part otherwise do the else part`
- if means that `if pc == "start" then the formula EQUALS the then formula, otherwise it EQUALS the else formula`
- The formule equals TRUE for: [i = 17, pc = "start", i' = 534, pc' = 'middle']
- The formule equals FALSE for: [i = 534, pc = "middle", i' = 77, pc' = 'done']
- The "no next values" replacement should be a formula that does not equal true for any value of `i`, `pc`, `i'`, and `pc'`.
- The simplest formula for that is `FALSE`. Most TLA+ keywords are uppercase.
IF pc = "start"
THEN (i' ∈ 0..1000) ⋏ (pc' = "middle")
ELSE IF pc = "middle"
THEN (i' = i + 1) ⋏ (pc' = "done")
ELSE FALSE
- This is a _pretty-printed_ TLA+ formula.
- The TLA+ source code is in ASCII so it doesn't have these Unicode glyphs
IF pc = "start"
THEN (i' \in 0..1000) /\ (pc' = "middle")
ELSE IF pc = "middle"
THEN (i' = i + 1) /\ (pc' = "done")
ELSE FALSE
The complete program is now a set of two formulas:
- Initial state formula:
`(i = 0) /\ (pc = "start")`
- Next state formula:
```
IF pc = "start"
THEN (i' \in 0..1000) /\ (pc' = "middle")
ELSE IF pc = "middle"
THEN (i' = i + 1) /\ (pc' = "done")
ELSE FALSE
```
But there is a nicer way to write the next-state formula
```
IF pc = "start"
THEN A
ELSE IF pc = "middle"
THEN B
ELSE FALSE
```
The formula is true in two cases:
- either `pc = "start" /\ A`
- or `pc != "middle" /\ B`
So we can rewrite it:
```
(pc = "start" /\ A) \/ (pc = "middle" /\ B)
```
putting the values back in:
```
((pc = "start") /\ A)
\/ ((pc = "middle" /\ B)
((pc = "start") /\ (i' \in 0..1000) /\ (pc' = "middle"))
\/ ((pc = "middle") /\ (i' = i + 1) /\ (pc' = "done"))
( (pc = "start")
/\ (i' \in 0..1000)
/\ (pc' = "middle") )
\/ ( (pc = "middle")
/\ (i' = i + 1)
/\ (pc' = "done") )
( pc = "start")
/\ i' \in 0..1000
/\ pc' = "middle" )
\/ ( pc = "middle"
/\ i' = i + 1
/\ pc' = "done" )
```
The far away parentheses are hard to read and will be harder on larger formula,
so TLA+ adds a prefix notation:
```
\/ /\ pc = "start"
/\ i' \in 0..1000
/\ pc' = "middle"
\/ /\ pc = "middle"
/\ i' = i + 1
/\ pc' = "done"
```
The initial state formula could also be written the same way:
```
(i = 0) /\ (pc = "start")
/\ i = 0
/\ pc = "start"
```
The "bulleted" list of "/\" is ended by any symbol to the left, here the `\/`,
meaning white space is significant.
Compare with the original C:
```C
int i;
void main() {
i = someNumberFrom0to1000();
i = i + 1;
}
```
- TLA+ is actually simpler
- The equal sign is an actual equality
- `someNumberFrom0to1000();` is non-deterministic. We need this because we
cannot predict in what order things happen
- Remember that this is a _formula_ not a sequence of commands:
- ` /\ ` and ` \/ ` are commutative (no shortcut eval)
## More syntax
- TLA specs appear in modules, like `MODULE SimpleProgram`
- Arithmetic like `+` and `..` can be imported from module `Integers`: `EXTENDS Integers`
- Identifiers need to be declared before use: `VARIABLES i, pc`
- The initial formula is defined by `Init ≜ (i = 0) /\ (pc = "start")`
- The next formula is defined similarly by `Next ≜ (the next formula)`
- Names `Init` and `Next` are conventional, but any name can be used
- ASCII version
```
------------- MODULE SimpleProgram -------------------
EXTENDS Integers
VARIABLES i, pc
Init == (i = 0) /\ (pc = "start")`
Next == \/ /\ pc = "start"
/\ i' \in 0..1000
/\ pc' = "middle"
\/ /\ pc = "middle"
/\ i' = i + 1
/\ pc' = "done"
=======================================================
```
## Decomposing large specs
For real code, the next state formula can be thousands of lines.
The math way to decompose is using definitions. Our spec is too small to be worth it,
but let's do it to show syntax.
```
Next == \/ /\ pc = "start"
/\ i' \in 0..1000
/\ pc' = "middle"
\/ /\ pc = "middle"
/\ i' = i + 1
/\ pc' = "done"
====== Is equivalent to this =======
Pick == /\ pc = "start"
/\ i' \in 0..1000
/\ pc' = "middle"
Add1 == /\ pc = "middle"
/\ i' = i + 1
/\ pc' = "done"
Next == Pick \/ Add1
```