a high-level language for modeling programs and systems—especially concurrent and distributed ones.
It's based on the idea that the best way to describe things precisely is with simple mathematics. TLA+ and its tools are useful for eliminating fundamental design errors, which are hard to find and expensive to correct in code.
We can model specify behaviors of systems, including computer processes, through descriptions of actions that update states.
x' = x + 1
x = x' + 1
x = y' ∧ y = x'
NB: for some reason, “=” is a boolean operator and “≜” is definitional equality. Assignment would correspond to an action.
TLA is a language that can be used to write statements that could be true or false within (at the starting point of) behaviors.
Intuitively:
One nuance. TLA is designed to ensure that behaviors always are allowed to “stutter”, i.e. fail to update anything. There seem to be at least two reasons for this.
Stuttering is supposed to be more realistic.
If behaviors can stutter, then “implementation is implication”
For example, a spec for a clock with an hour hand and a minute hand will be implied by a spec for a clock with an hour, minute, and second hand, in the sense that every behavior satisfying the second spec satisfies the first.
The idea is to ensure that you can leave out temporal detail in higher-level specifications, and then add it in without needing to backtrack.
Some formal results here, Lamport/Cohen reduction theorem.
Predicate ≜ Action with no primed variables | ENABLED Action Form ≜ Predicate | □[Action]ₜ | ¬Form | Form ∧ Form | □Form
Where:
[Φ]ₜ
abbreviates Φ ∨ t'=t
TLA+ extends the above (simple TLA) by adding general quantification, sets, and specific operators for use in actions.
The main tool for using TLA+ is the tlc
model checker.
Given a sufficiently descriptive statement in TLA+,
tlc
can attempt to generate a representation of
all possible behaviors that conform to that statement.
The representation (I think) is called a model
“Sufficiently descriptive” means basically that the statement gives an initial assignment to each declared variable, and gives an update relation that pins down how state changes.
Init ≜ V = 0 // Nat ∧ pc = "Plus" // program counter Plus ≜ pc = "Plus" ∧ pc' = "Minus" ∧ V' = V + 1 Minus ≜ pc = "Minus" ∧ pc' = "Plus" ∧ V' = V - 1 Next ≜ Plus ∨ Minus Spec ≜ Init ∧ □[Next]ₜ
Models have a general graph structure; kind of like automata, with behaviors corresponding to automata runs.
tlc
can also do some extra checks on the model:
The TLA+ language is pretty simple.
But if you want to model algorithms, writing in pure TLA+ still introduces a layer of indirection.
One possible solution is to use a DSL for representing an algorithm, and then automatically translate to TLA+.
variable seq = <<1, 2, 3, 2>>; index = 1; seen = {}; is_unique = TRUE; begin TheMainLoop: while index <= Len(seq) do if seq[index] \notin seen then seen := seen \union {seq[index]}; else is_unique := FALSE; end if; index := index + 1; end while; end algorithm;
variable seq = <<1, 2, 3, 2>>;
index = 1;
seen = {};
is_unique = TRUE;
begin
TheMainLoop:
while index <= Len(seq) do
if seq[index] \notin seen then
seen := seen \union {seq[index]};
else
is_unique := FALSE;
end if;
index := index + 1;
end while;
end algorithm;
I tried out comparing two algorithms in TLA+.
Basic Method:
Basically works, probably wasn't a great idea.
I think the main kind of algorithm comparison that TLA+ is intended for is refinement.
One develops a program through a series of refinements, starting from a high-level algorithm and eventually reaching a low-level program. Just as we went from Program 1 to the finier-grained Program 2… the entire process from specification to C code could in principle be carried out in TLA. “All” we would need is a precise semantics of C, which would allow the translation of any C program into a TLA formula.