235 lines
7.1 KiB
Markdown
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
|
|
```
|