Assignments in Apalache
Any run of Apalache requires an operator name as the value for the parameter
--next
(by default, this value is "Next"
). We refer to this operator as the
transition operator (or transition predicate).
Actions, Slices and Minimal Actions
Actions
In TLA+, an action is any Boolean-valued expression or operator, that
contains primed variables (e.g. Next
). For the sake of this definition,
assume UNCHANGED x
is just syntactic sugar for x' = x
. Intuitively,
actions are used to define the values of state variables after a transition,
for example:
VARIABLE x
...
Next == x' = x + 1
The state transition described by Next
is fairly obvious; if x
has the
value of 4
in the current state, it will have the value of 5
in any
successor state. This brings us to the first natural requirement by Apalache:
the transition operator must be an action.
Successor State Encodings
Unfortunately, the notion of an action is too broad to be a sufficient requirement for the transition operator. Consider this slight modification of the above example:
VARIABLE x, y (* new variable *)
...
Next == x' = x + 1
Just as in the first example, the expression x' = x + 1
is, by definition, an
action. However, since the second example defines a state variable y
, this
action is no longer a sufficient description of a relation between a current
state and a successor state; it does not determine a successor value y'
.
This brings us to the second requirement: the transition operator must allow
Apalache to directly encode the relation between two successive states. This
captures two sub-requirements: firstly, we disallow transition operators which
fail to specify the value of one or more variables in the successor states,
like the one in the example above. Secondly, we also disallow transition
operators where the value of a successor state variable is determined only by
implicit equations. Consider the following two cases:
VARIABLE y
...
A == y' = 1
B == y' * y' - 2 * y' + 1 = 0
Using some basic math, we see that action B
can be equivalently written as
(y' - 1)*(y' - 1) = 0
, so it describes the exact same successor state, in
which the new value of y
is 1
. What makes it different from action A
is
the fact that this is far from immediately obvious from the syntax. The fact
that there happened to be a closed-form solution for which gave us an integer
value for y'
, is a lucky coincidence, as B
could have been, for example,
y' * y' + 1 = 0
, with no real roots. To avoid cases like this, we require
that transition operators explicitly declare the values of state variables in
successor states.
We call syntactic forms, which explicitly represent successor state values,
assignment candidates. An assignment candidate for x
is a TLA+ expression
that has one of the following forms:
x' = e
,x' \in S
,UNCHANGED x
, orx' := e
(note that:=
is the operator defined in Apalache.tla)
So to reformulate the second requirement: the transition operator must contain at least one assignment candidate for each variable declared in the specification.
Control Flow: Minimal and Compound Actions
When writing non-trivial specifications, authors often end up with something similar to the following:
EventA == ...
EventB == ...
...
Next == \/ EventA
\/ EventB
Specifically, EventA
and EventB
often represent mutually exclusive
possibilities of execution. Just like before, the basic definition of an
action is not sufficient to explain the relation of EventA
or EventB
and
Next
; if EventA
is an action and EventB
is an action, then Next
is also
an action. To more accurately describe this scenario, we observe that the
operator or ( \/
) sometimes serves as a kind of parallel composition operator
(||
) in process algebra - it connects two (or more) actions into a larger
one.
There are only two operators in TLA+ that could be considered control-flow
operators in this way, the or (\/
) operator and the if-then-else operator.
We distinguish their uses as action- and as value operators:
A == x = 1 \/ x = 2 (* arguments are not actions *)
B == x' = 1 \/ x' = 2 (* arguments are actions *)
Simply put, if all arguments to an operator \/
are actions, then that
operator is an action-or, otherwise it is a value-or. Similarly, if both the
THEN _
and ELSE _
subexpressions of if-then-else are actions, it is an
action-ITE, otherwise it is a value-ITE (in particular, a value-ITE can be
non-Boolean).
Using these two operators we can define the following terms: A minimal action is an action which contains no action-or and no action-ITE. Conversely, a compound action is an action which contains at least one action-or or at least one action-ITE.
Slices
Given a transition operator, which is most commonly a compound action, we can decompose it into as many minimal actions as possible. We call this process slicing and the resulting minimal actions slices. This allows us to write transition operators in the following equivalent way:
Next == \/ Slice1
\/ Slice2
...
\/ SliceN
Where each Slice[i]
is a minimal action.
The details of slicing are nuanced and depend on operators other than or (\/
)
and if-then-else, but we give two examples here:
If a formula A
has the shape A1 \/ ... \/ An
(where A1
, ... An
are
actions), then a slice of A has the shape Si
, where Si
is a slice of some
Ai
.
If a formula A
has the shape IF p THEN A1 ELSE A2
(where A1
, A2
are
actions), then a slice of A has the shape p /\ S1
or \neg p /\ S2
, where
S1
is a slice of A1
and S2
is a slice of S2
.
Slices allow us to formulate the final requirement: the transition operator must be such, that we can select one assignment candidate for each variable in each of its slices (minimal actions) as an assignment. The process and conditions of selecting assignments from assignment candidates is described in the next section.
Assignments and Assignment Candidates
Recall, an assignment candidate for x
is a TLA+ expression that has one of the following forms:
x' = e
,x' \in S
,UNCHANGED x
, orx' := e
(note that:=
is the operator defined inApalache.tla
)
While a transition operator may contain multiple assignment candidates for the same variable, not all of them are chosen as assignments by Apalache. The subsections below describe how the assignments are selected.
Minimality
Assignments aren't spurious; each variable must have at least one assignment per transition operator, but no more than necessary to satisfy all of the additional constraints below (i.e. no more than one assignment per slice).
If all possible slices fail to assign one or more variables, an error, like the one below, is reported:
Assignment error: No assignments found for: x, z
Such errors are usually the result of adding a VARIABLE
without any
accompanying TLA+ code relating to it. The case where at least one transition,
but not all of them, fails to assign a variable is shown below.
Syntax Order
For the purpose of evaluating assignments, Apalache considers the left-to-right
syntax order of and-operator (/\
) arguments. Therefore, as many assignments
as possible are selected from the first (w.r.t. syntax order) argument of and
(/\
), then from the second, and so on.
Example:
Next == x' = 1 /\ x' = 2
In the above example, x' = 1
would be chosen as an assignment to x
, over
x' = 2
.
Assignment-before-use Convention
If, in the syntax order defined above, an expression containing a primed
variable x'
syntactically precedes an assignment to x
, the assignment
finder throws an exception of the following shape:
Assignment error: test.tla:10:16-10:17: x' is used before it is assigned.
notifying the user of any variables used before assignment. In particular,
right-hand-sides of assignment candidates ( e.g. x' + 2
in y' = x' + 2
)are
subject to this restriction as well. Consider:
A == x' > 0 /\ x' = 1
B == y' = x' + 2 /\ x' = 1
In A
, the expression x' > 0
precedes any assignment to x
and in B
,
while y' = x' + 2
is an assignment candidate for y
, it precedes any
assignment to x
, so both expressions are inadmissible (and would trigger
exceptions).
Note that this only holds true if A
(resp. B
) is chosen as the transition
operator. If A
is called inside another transition operator, for example in
Next == x' = 1 /\ A
, no error is reported.
Balance
In cases of the or-operator (\/
), all arguments must have assignments for the
same set of variables. In particular, if one argument contains an assignment
candidate and another does not, such as in this example:
\/ y = 1
\/ y' = 2
the assignment finder will report an error, like the one below:
Assignment error: test.tla:10:15-10:19: Missing assignments to: y
notifying the user of any variables for which assignments exist in some, but
not all, arguments to \/
. Note that if we correct and extend the above
example to
/\ \/ y' = 1
\/ y' = 2
/\ y' = 3
the assignments to y
would be y' = 1
and y' = 2
, but not y' = 3
;
minimality prevents us from selecting all three, the syntax order constraint
forces us to select assignments in y' = 1 \/ y' = 2
before y' = 3
and
balance requires that we select both y' = 1
and y' = 2
. On the other hand,
if we change the example to
/\ y' = 3
/\ \/ y = 1
\/ y' = 2
the only assignment has to be y' = 3
. While one of the disjuncts is an
assignment candidate and the other is not, the balance requirement is not
violated here, since neither disjunct is chosen as an assignment.
Similar rules apply to if-then-else: both the THEN _
and ELSE _
branch must
assign the same variables, however, the IF _
condition is ignored when
determining assignments.
Assignment-free Expressions
Not all expressions may contain assignments. While Apalache permits the use of
all assignment candidates, except ones defined with :=
(details
here), inside other expressions, some of these candidates will never
be chosen as assignments, based on the syntactic restrictions outlined below:
Given a transition operator A
, based on the shape of A
, the following holds:
- If
A
has the shapeA_1 /\ ... /\ A_n
, then assignments are selected fromA_1, ... , A_n
sequentially, subject to the syntax-order rule. - If
A
has the shapeA_1 \/ ... \/ A_n
, then assignments are selected in allA_1, ... , A_n
independently, subject to the balance rule. - If
A
has the shapeIF p THEN A_1 ELSE A_2
, then:p
may not contain assignments. Any assignment candidates inp
are subject to the assignment-before-use rule.- Assignments are selected in both
A_1
andA_n
independently, subject to the balance rule.
- If
A
has the shape\E x \in S: A_1
, then:S
may not contain assignments. Any assignment candidates inS
are subject to the assignment-before-use rule.- Assignments are selected in
A_1
- In any other case,
A
may not contain assignments, however, any assignment candidates inA
are subject to the assignment-before-use rule.
Examples:
A == /\ x' = 2
/\ \E s \in { t \in 1..10 : x' > t }: y' = s
Operator A
contains assignments to both x
and y
; while x' > t
uses
x'
, it does not violate the assignment-before-use rule, since the assignment
to x
precedes the expression, w.r.t. syntax order.
(* INVALID *)
B == \E s \in { t \in 1..10 : x' > t }: y' = s
In operator B
, the assignment to x
is missing, therefore x' > t
produces
an error, as it violates assignment-before-use.
C == /\ x' = 1
/\ IF x' = 0 /\ 2 \in {x', x' + 2, 0}
THEN y' = 1
ELSE y' = 2
The case in C
is similar to A
; conditions of the if-then-else operator may
not contain assignments to x
, so x' = 0
can never be one, but they may use
x'
, since a preceding expression (x' = 1
) qualifies as an assignment.
(* INVALID *)
D == IF x' = 0
THEN y' = 1
ELSE y' = 2
The operator D
produces an error, for the same reason as B
; even though x' = 0
is an assignment candidate, if-conditions are assignment-free, so x' = 0
cannot be chosen as an assignment to x
.
(* INVALID *)
E == /\ x' = 2
/\ \A s \in { t \in 1..10 : x' > t }: y' = s
Lastly, while E
looks almost identical to A
, the key difference is that
expressions under universal quantifiers may not contain assignments. Therefore,
y' = s
is not an assignment to y
and thus violates assignment-before-use.
Manual Assignments
Users may choose, but aren't required, to use manual assignments x' := e
in
place of x' = e
. While the use of this operator does not change Apalache's
internal search for assignments (in particular, using manual assignment
annotations is not a way of circumventing the syntax order requirement), we
encourage the use of manual assignments for clarity.
Unlike other shapes of assignment candidates, whenever a manual assignment is used in a position where the assignment candidate would not be chosen as an assignment (either within assignment-free expressions or in violation of, for example, the syntax order rule) an error, like one of the two below, is reported:
Assignment error: test.tla:10:12-10:18: Manual assignment is spurious, x is already assigned!
or
Assignment error: test.tla:10:15-10:21: Illegal assignment inside an assignment-free expression.
The benefit of using manual assignments, we believe, lies in synchronizing the user's and the tool's understanding of where assignments happen. This helps prevent unexpected results, where the user's expectations or intuition regarding assignment positions are incorrect.
Note: To use manual assignments where the assignment candidate has the shape of
x' \in S
use \E s \in S: x' := s
.