More Operators

This isn’t much more complicated than the last sections, but this is the best place to put it. So we can now model concurrent algorithms and write complex liveness properties. You know what we can’t do?

Sum up a sequence.

I mean sure, we can write a Pluscal algorithm to do that:

---- MODULE sum ----
EXTENDS Integers, Sequences, TLC, FiniteSets
CONSTANT S
ASSUME S \subseteq Int

SumSeq(s) == 0 \* ???

(*--algorithm dup
variable 
  seq \in [1..5 -> S];
  sum = 0;
  i = 1;

define
  TypeInvariant ==
    /\ sum \in Int
    /\ i \in 1..Len(seq)+1

  IsCorrect == pc = "Done" => sum = SumSeq(seq)
end define; 

macro add(x, val) begin
  x := x + val
end macro;

begin
  Iterate:
    while i <= Len(seq) do
      add(sum, seq[i]);
      add(i, 1);
    end while;
end algorithm; *)
====

But how the heck do you write SumSeq?!

We can do this with what we’ve learned so far, but it’s really complicated and annoying and I don’t expect you to figure it out on your own. Maybe I’ll push it to a topic or something. But motivated by the annoyingness of summing sequences, in 2008 TLA+ was updated to include recursive operators.

Recursive Operators

Recursive operators have to be predefined, like this:

RECURSIVE SumSeq(_)

The (_) is the number of arguments you want in advance. So for a 2-parameter operator, you’d write Op(_, _). Once you’ve done this, you can define the operator as normal, except it’s able to reference itself:

SumSeq(s) == IF s = <<>> THEN 0 ELSE
  Head(s) + SumSeq(Tail(s))

Easy. In fac we can put the RECURSIVE part inside a LET, to make a helper op:

SumSeq(s) == LET
  RECURSIVE Helper(_)
  Helper(s_) == IF s_ = <<>> THEN 0 ELSE
  Head(s_) + Helper(Tail(s_))
IN Helper(s)

There’s no syntactic check that the recursion ends. If you have unbound recursion then TLC will throw a stack overflow error.

Recursion on a set and commutativity

If we want to do the same thing with summing a set, we need a way to pick a single element for recursion. Naturally, the easiest way to do that is with CHOOSE.

RECURSIVE SetSum(_)

SetSum(set) == IF set = {} THEN 0 ELSE
  LET x == CHOOSE x \in set: TRUE
    IN x + SetSum(set \ {x})

Seeing this should worry you. There isn’t a unique choice of x: every element of the set satisfies TRUE! This means that TLC will choose the lowest value.

RECURSIVE SetToSeq(_)

SetToSeq(set) == IF set = {} THEN <<>> ELSE
  LET x == CHOOSE x \in set: TRUE
    IN <<x>> \o SetToSeq(set \ {x})
>>> SetToSeq({6, 8, 1, 2, -1, 5})

<<-1, 1, 2, 5, 6, 8>>

Sometimes this doesn’t matter like in SetSum. But if it might, you should specify a unique selection predicate for your choose, like explicitly choosing the minimum value.

Higher-order Operators

Next up: operators that take other operators as functions. That way we can have SeqMap. Here’s how we can define SeqMap with a function:

SeqMap(f, seq) == [i \in DOMAIN seq |-> f[seq[i]]]

But if we want to use an operator, we’d write it this way instead:

SeqMap(Op(_), seq) == [i \in DOMAIN seq |-> Op([seq[i]])]

Not too bad. You can define anonymous operators with LAMBDA:

SeqMap(LAMBDA x: x + 1, <<1, 2, 3>>)
\* <<2, 3, 4>>

Warning

You can’t combine recursive and higher-order operators.

Binary operators

If you peek at the definition of the Sequences module, you’ll see how it defines \o:

s \o t == [i \in 1..(Len(s) + Len(t)) |-> IF i \leq Len(s) THEN s[i]
                                                         ELSE t[i-Len(s)]]

\o is a binary operator. There’s a fixed set of binary operators, like \o, +, and \prec, that you can define. On the whole I don’t like doing this because it makes specs confusing, but there’s a couple I use often:

set ++ x == set \union {x}
set -- x == set \ {x}

Function Operators

This is a bit of syntactic sugar for writing top-level functions:

Double == [x \in 1..10 |-> x * 2]

\* can also be written as

Double[x \in 1..10] == x * 2

This is primarily for writing recursive functions:

Factorial[x \in 0..10] == IF x = 0 THEN 1 ELSE x * Factorial[x - 1]

CASE

I had nowhere else to put this so I’m just dumping it here for completion’s sake.

Fizzbuzz(x) ==
  CASE (x % 3 = 0) /\ (x % 5 = 0) -> "Fizzbuzz"
    [] (x % 3 = 0)                -> "Fizz"
    [] (x % 5 = 0)                -> "Buzz"
    [] OTHER                      -> x

If nothing matches (and you didn’t have an OTHER), then TLC raises an error. If more than one thing matches, it’s implementation-defined what actually is executed, and TLC will pick the first choice that matches.