What you have come across is an example of what's called algebraic
specification. This is a way of specifying data structures that was
popular in the 70s and 80s. I believe that most people, including I
believe most mathematicians, would describe a stack as a finite
sequence of elements and describe the operations of push, pop, and top
in terms of sequences. In algebraic specification, one starts with a
bunch of axioms on push, pop, top, and the empty stack, and defines a
stack as something you get by performing a sequence of push operations
on the empty stack. This is supposed to be better because it's more
abstract and doesn't depend on a concrete representation of a stack
as a sequence.
Thinking that this kind of abstraction is better is rather silly,
since the two ways of defining stack are isomorphic and one can define
the "concrete" representation in terms of the "abstract" one. In
practice, algebraic specifications don't work for anything but a few
simple data structures like stacks. It's very hard for mathematicians
to figure out the correct set of axioms, and impossible for engineers.
I remember years ago talking to someone who worked on this kind of
specification, and he said that you should specify a small number of
primitive data types this way and define anything else "concretely" in
terms of them. You can think of TLA+ this way, where sets and
functions are defined algebraically and other data structures are
defined in terms of them. Algebraic specification seems to have
fallen out of fashion.
if you push an x and then applies any sequence of "push" and "pop"
("push" and "pop" in equal number) you will retrieve your x with
"top".
You need an additional requirement--e.g., it's not true if you perform
all the pops first. Perhaps this illustrates the difficulty of using
algebraic specification.
As Ron pointed out, what whoever wrote that spec you found was doing
was defining a data structure, not a machine/program. For any data
structure, you can define a machine that takes its operations as input
and produces results as output. I suggest doing that for an arbitrary
data structure rather than specializing it for a stack.
Leslie
PP(S1, x, s, n) == INSTANCE PushPop
THEOREM
\A s1 \in S, x \in X : \EE s, n : PP(s1, x, s, n)!PP
--
You received this message because you are subscribed to the Google Groups "tlaplus" group.
To unsubscribe from this group and stop receiving emails from it, send an email to tlaplus+u...@googlegroups.com.
To post to this group, send email to tla...@googlegroups.com.
Visit this group at https://groups.google.com/group/tlaplus.
For more options, visit https://groups.google.com/d/optout.
this is not what you want, and in fact, that theorem doesn't mean much: PP(...)!PP is an implication, so for it to be true it's enough to choose s and n such that PP(...)!Spec is false. For example, it is true for a variable n that is 42 initially. What you really mean is\A s1 \in S, x \in X : \AA s, n : PP(s1, x, s, n)!PP
Now, coming back to the original discussion, the question remains how you would actually prove theorem PP. Obviously, you need some form of induction, intuitively relying on an invariant saying that n equals the length of the sequence of elements that the stack contains above the element that you pushed initially, and that no execution satisfying Spec ever touches the initial part of the stack. But since S is kept abstract, there is no way of talking about that length or that part of the stack directly. People working on algebraic specifications introduced a whole theory of initial models (and also, dually, of final models) that give you suitable inductive proof rules, and various conditions that ensure when a specification has such models.
Now, you can implement the stack as you like and need only prove that it behaves as if there were a sequence s and counter n. I believe that is essentially what Leslie meant when he wrote that the algebraic and the model-based styles of specifying a stack were isomorphic.
Thinking that this kind of abstraction is better is rather silly,
since the two ways of defining stack are isomorphic and one can define
the "concrete" representation in terms of the "abstract" one.
You need an additional requirement--e.g., it's not true if you perform
all the pops first. Perhaps this illustrates the difficulty of using
algebraic specification.
It is true that the specification stated like this commits to implementing the stack as a sequence, which may not be what you want. The abstract spec is easily obtained by hiding s and n, writingSpec == \EE s,n : SpecNow, you can implement the stack as you like and need only prove that it behaves as if there were a sequence s and counter n.
I believe that is essentially what Leslie meant when he wrote that the algebraic and the model-based styles of specifying a stack were isomorphic.
Now, coming back to the original discussion, the question remains how you would actually prove theorem PP. Obviously, you need some form of induction, intuitively relying on an invariant saying that n equals the length of the sequence of elements that the stack contains above the element that you pushed initially, and that no execution satisfying Spec ever touches the initial part of the stack. But since S is kept abstract, there is no way of talking about that length or that part of the stack directly.
they have what they call model variables or ghost variables and the formula
Now, coming back to the original discussion, the question remains how you would actually prove theorem PP. Obviously, you need some form of induction, intuitively relying on an invariant saying that n equals the length of the sequence of elements that the stack contains above the element that you pushed initially, and that no execution satisfying Spec ever touches the initial part of the stack. But since S is kept abstract, there is no way of talking about that length or that part of the stack directly. People working on algebraic specifications introduced a whole theory of initial models (and also, dually, of final models) that give you suitable inductive proof rules, and various conditions that ensure when a specification has such models.
This I don't understand. Is there no way to do the induction structurally, based on the axioms to (as you'd do in algebraic proofs)? The invariant is true in the initial condition based on the first axiom, and then you can introduce a sequence of elements (ys) for the sake of the proof, while making no further assumptions about S. So you can talk about that part of the stack as a sequence (literally) of algebraic operations (push) without exploring S (and then Len(ys) >= 0).
\* Algebraic structure
ASSUME /\ push \in [S \X X -> S]
/\ pop \in [S -> S]
/\ top \in [S -> X]
\* Algebraic axioms
ASSUME \A x \in X, s \in S :
/\ top[push[<<x, s>>]] = x
/\ pop[push[<<x, s>>]] = s
In particular, there is no formal basis for asserting that the parameter set S is constructed by (the empty stack and) the push operation. Also, there could be other operations on stacks.
Finally, it looks like you can augment your specification with a history variable y that records the sequence of elements that have been pushed on top of the element x pushed in the initial condition but not yet popped off and prove that the stack s in the specification behaves like that sequence y (i.e., top and pop applied to s yield the same values as the analogous operations applied to y).
P.S.
Also, due to my lack of algebra/logic/model-theory knowledge, I don't understand why your axiom about the uniqueness of S is necessary.
Anyway, I think that this exercise proves Leslie's point about the difficulty of the algebraic/axiomatic approach for engineers (like me), not to speak of the difficulty of model-checking such specifications (BTW, are there model checkers for that approach?)
On 05 Jun 2016, at 08:06, Ron Pressler <r...@paralleluniverse.co> wrote:
On Saturday, June 4, 2016 at 10:31:03 AM UTC+3, Stephan Merz wrote:In particular, there is no formal basis for asserting that the parameter set S is constructed by (the empty stack and) the push operation. Also, there could be other operations on stacks.
That's true, and indeed the axioms aren't complete. I believe I understand how your axioms specify S in terms of the operations operating on it and in terms of X. Suppose we have this operator:
RECURSIVE _ ** _
f ** k ==
IF k = 0 THEN [t \in DOMAIN f |-> t]
ELSE [t \in DOMAIN f |-> f[(f ** (k-1))[t]]]
Then your axioms are absolutely necessary to prove the theorem:
\A s \in Stack(X) : \E n \in Nat : (pop ** n)[s] = empty(X)
So I think I understand your point about the necessity for a "framework" to support algebraic reasoning (and agree that it is probably not worthwhile in TLA+).
But just for the sake of the exercise, I'm not sure that all of that is required for FL's particular theorem. More below.
Finally, it looks like you can augment your specification with a history variable y that records the sequence of elements that have been pushed on top of the element x pushed in the initial condition but not yet popped off and prove that the stack s in the specification behaves like that sequence y (i.e., top and pop applied to s yield the same values as the analogous operations applied to y).
Right, that was my original idea, but perhaps we can do something much simpler:
Inv == top[(pop ** n)[s]] = x
where n and s are the variables in my PushPop module. I think that the invariant Inv is an inductive invariant, that it proves the theorem, and that no further axioms than "my" two (specifically with regards to the empty stack and the construction of S) are necessary to prove Inv.
Finally, while your axiomatization constructs S and its operations "from scratch" in terms of X and the axioms, wouldn't it be easier in practice to do something like the following, instead of CHOOSEing the functions etc.:
CONSTANTS X, \* The set of elements
S, \* The set of stacks
push, pop, top, EMPTY
ASSUME /\ EMPTY \in S
/\ push \in [S \X X -> S]
/\ pop \in [S -> S]
/\ top \in [S -> X]
/\ \A x \in X, s \in S : push[<<x, s>>] \in S \* closure
/\ \A x \in X, s \in S : push[<<x, s>>] /= EMPTY
/\ \A x \in X, s \in S : top[push[<<x, s>>]] = x
/\ \A x \in X, s \in S : pop[push[<<x, s>>]] = s
/\ \A s \in S : s = EMPTY \/ \E s1 \in S, x \in X : s = push[<<x, s1>>]
Doesn't this specify S sufficiently in terms of X and the operations, rather than constructing S as you've done?
Ron
On constructing the data structure vs. relying on assumptions: I think both have their place. For example, in mathematics one typically constructs, say, the real numbers via Dedekind cuts, but presents algebraic structures such as groups through characteristic axioms. For our purposes, presentation through ASSUME leaves more freedom for later implementations of the data structure later, since only the stated properties have to be satisfied – but knowing when you have stated enough properties can be difficult.
Case in point: your last conjunct (trivially) follows from the induction rule for S that was included in my specification, but does not imply that rule. To see this, consider a structure where S is the set of finite *and infinite* sequences over X
On constructing the data structure vs. relying on assumptions: I think both have their place. For example, in mathematics one typically constructs, say, the real numbers via Dedekind cuts, but presents algebraic structures such as groups through characteristic axioms. For our purposes, presentation through ASSUME leaves more freedom for later implementations of the data structure later, since only the stated properties have to be satisfied – but knowing when you have stated enough properties can be difficult.
Doesn't your approach essentially use CHOOSE to magically choose a model (basically, pull it out of thin air) in case the axiomatization isn't complete, in a way that is equally opaque to proofs? I mean, in the end, all the proofs have to work with is your assumptions, not any other property of a model that's been CHOsen (unless maybe those properties are known through existing algebras, like Nat ???)