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

7.1 KiB

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:

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