3. Smallstep: Small-step Operational Semantics
The hiding lean (above in the source file) should not be needed any more and should be removed from all files everywhere it exists.
This chapter adapts Smallstep to follow Slang, the initial part of Imp, on just Aexp and Bexp (without variables). This means that parts of this chapter had to adjust: Concurrent Imp is dropped in favor of Nondeterministic Aexp, and the stack machine is simplified to just Aexps without variables.
In this and later chapters, we are not very consistent about presenting computation rules first and congruence rules after...
HIDE: Sometime in the early 2010s, we did some mining past exams for exercises...
-
Loris: No interesting exercise in Finals of 2007-2009-2010-2011. Nothing in second midterms except for 2011.
-
2011 midterm proposes the following exercise: give the small step relation of FLIP X (alternatively HAVOC, ANYTHING). We could then ask to extend the proof of equivalence of big step vs small step (personally don't like it too much).
-
Maybe we can ask how they would adapt the definition of Hoare triple to small step (maybe in the exam).
HIDE: BCP: I also have a bunch of slides from earlier offerings of CIS500 that might be good additions to the TERSE notes.
HIDE: Possible major restructuring: This chapter might better be postponed to later in the course. A big-step presentation of STLC (and maybe even some of the extensions like subtyping?) could come first. However, this would invite a much bigger change, where all the variants of STLC (with refs, with subtyping, ...) are done in big-step style. This requires more thought...
HIDE: Wonder whether it would be interesting to show them how to make a correspondence with a "real abstract machine" at a lower level...? There's a start at an exercise along these lines below.
3.1. Big-step and Small-step Evaluation
The evaluators we saw for Slang were formulated in a "big-step" style: they specify how a given expression can be evaluated to its final value "all in one big step":
2 + 2 + 3 * 4 ⇓ 16
This style is simple and natural for many purposes — indeed, Gilles Kahn, who popularized it, called it natural semantics. But there are some things it does not do well. In particular, it does not give us a convenient way of talking about concurrent programming languages, where the semantics of a program — the essence of how it behaves — includes not just which input states get mapped to which output states, but also the intermediate states that it passes through along the way; this is crucial, since these states can also be observed by concurrently executing code.
Another shortcoming of the big-step style is more technical but equally
critical in many situations. Suppose we want to define a variant of our
expression language where a value could be either a number or a list of
numbers. In the syntax of this extended language, it will be possible to
write strange expressions like 2 + nil, and our semantics for arithmetic
expressions will then need to say something about how such expressions
behave. One possibility is to maintain the convention that every arithmetic
expression evaluates to some number by choosing some way of viewing a list
as a number — e.g., by specifying that a list should be interpreted as 0
when it occurs in a context expecting a number. But this would be a bit of
a hack.
A much more natural approach is simply to say that the behavior of the
expression 2 + nil is undefined — i.e., it doesn't evaluate to any
result at all. And we can easily do this: we just have to formulate aeval
and beval as inductive propositions rather than functions, so that we can
make them partial functions instead of total ones.
Now, however, we encounter a subtlety that will become important once we move to a full programming language with looping. There, a program might fail to produce a result for two quite different reasons: either because the execution gets into an infinite loop or because, at some point, the program tries to do an operation that makes no sense, such as adding a number to a list, so that none of the evaluation rules can be applied.
These two outcomes — nontermination vs. getting stuck in an erroneous configuration — should not be confused. In particular, we want to allow the first (because permitting the possibility of infinite loops is the price we pay for the convenience of programming with general looping constructs) but prevent the second (which is just wrong), for example by adding some form of typechecking to the language. Indeed, this will be a major topic of the next chapter, on types. As a first step, we need a way of presenting the semantics that allows us to distinguish nontermination from erroneous "stuck states."
So, for lots of reasons, we'd like to have a finer-grained way of defining
and reasoning about program behaviors. This is the topic of the present
chapter. Our goal is to replace the "big-step" Eval relation with a
"small-step" relation that specifies, for a given program, how its atomic
steps of computation are performed. In the small-step style, we show how
to "reduce" an expression to a simpler form by performing a single step of
computation:
2 + 2 + 3 * 4 ⟶ 2 + 2 + 12 ⟶ 4 + 12 ⟶ 16
3.2. A Toy Language
To save space, we start with an incredibly simple language of just
constants and addition. (We use single-letter constructors c and p
— for Constant and Plus — for brevity.) The same techniques scale up to
richer languages.
inductive Tm where
| c (n : Nat) -- Constant
| p (t₁ t₂ : Tm) -- Plus
A standard big-step evaluator, as a function.
def evalF (t : Tm) : Nat :=
match t with
| .c n => n
| .p t₁ t₂ => evalF t₁ + evalF t₂
Here is the same evaluator, written in exactly the same style, but formulated as an
inductively defined relation. We use the notation t ⇓ n for "t evaluates to n."
The notation command below is how that is declared: it introduces ⇓ as
infix syntax for the Eval relation defined with it, with a precedence saying
how tightly it binds.
This is the lightweight way to name a relation; later chapters, where a whole
object language needs a grammar rather than a single operator, reach for
declare_syntax_cat instead.
------- (const)
c n ⇓ n
t₁ ⇓ n₁
t₂ ⇓ n₂
----------------- (plus)
p t₁ t₂ ⇓ n₁ + n₂
inductive Eval : Tm → Nat → Prop where
| const (n : Nat) : Eval (.c n) n
| plus (t₁ t₂ : Tm) (n₁ n₂ : Nat) (h₁ : Eval t₁ n₁) (h₂ : Eval t₂ n₂) : Eval (.p t₁ t₂) (n₁ + n₂)
notation:50 t " ⇓ " n => Eval t n
Now, here is the corresponding small-step relation, written t ⟶ t':
------------------------------- (plus)
p (c n₁) (c n₂) ⟶ c (n₁ + n₂)
t₁ ⟶ t₁'
-------------------- (plusLeft)
p t₁ t₂ ⟶ p t₁' t₂
t₂ ⟶ t₂'
---------------------------- (plusRight)
p (c n₁) t₂ ⟶ p (c n₁) t₂'
namespace SimpleArith1
inductive Step : Tm → Tm → Prop where
| plus (n₁ n₂ : Nat) :
Step (.p (.c n₁) (.c n₂)) (.c (n₁ + n₂))
| plusLeft (t₁ t₁' t₂ : Tm)
(h : Step t₁ t₁') :
Step (.p t₁ t₂) (.p t₁' t₂)
| plusRight (n₁ : Nat) (t₂ t₂' : Tm)
(h : Step t₂ t₂') :
Step (.p (.c n₁) t₂) (.p (.c n₁) t₂')
scoped notation:40 t:41 " ⟶ " t':41 => Step t t'
Things to notice:
-
We are defining a single reduction step, in which just one
pnode is replaced by its value. -
Each step finds the leftmost
pnode that is ready to go (both of its operands are constants) and rewrites it in place. The first rule tells how to rewrite thispnode itself; the other two rules tell how to find it. -
A term that is just a constant cannot take a step.
Let's pause and check a couple of examples of reasoning with the step relation.
If t₁ steps to t₁', then p t₁ t₂ steps to p t₁' t₂.
example :
(.p
(.p (.c 1) (.c 3))
(.p (.c 2) (.c 4))) ⟶
(.p
(.c 4)
(.p (.c 2) (.c 4))) := ⊢ ((Tm.c 1).p (Tm.c 3)).p ((Tm.c 2).p (Tm.c 4)) ⟶ (Tm.c 4).p ((Tm.c 2).p (Tm.c 4))
⊢ (Tm.c 1).p (Tm.c 3) ⟶ Tm.c 4; All goals completed! 🐙
Right-hand sides step only once the left side is a value.
example :
(.p
(.c 0)
(.p
(.c 2)
(.p
(.c 1)
(.c 3))))
⟶
(.p
(.c 0)
(.p
(.c 2)
(.c 4))) := ⊢ (Tm.c 0).p ((Tm.c 2).p ((Tm.c 1).p (Tm.c 3))) ⟶ (Tm.c 0).p ((Tm.c 2).p (Tm.c 4))
solution!
⊢ (Tm.c 2).p ((Tm.c 1).p (Tm.c 3)) ⟶ (Tm.c 2).p (Tm.c 4); ⊢ (Tm.c 1).p (Tm.c 3) ⟶ Tm.c 4; All goals completed! 🐙
To what does the following term step?
.p
(.p
(.c 1)
(.c 2))
(.p
(.c 1)
(.c 2))
(A) .c 6
(B) .p (.c 3) (.p (.c 1) (.c 2))
(C) .p (.p (.c 1) (.c 2)) (.c 3)
(D) .p (.c 3) (.c 3)
(E) None of the above
Show solution
(B) .p (.c 3) (.p (.c 1) (.c 2))
What about this one?
.c 1
(A) .c 1
(B) .p (.c 0) (.c 1)
(C) None of the above
Show solution
(C) None of the above
end SimpleArith1
3.3. Relations
We will be working with several different single-step relations, so it is
helpful to generalize a bit and state a few definitions and theorems about
relations in general. (The optional chapter Rel in Logical Foundations
develops some of these ideas in a bit more detail; reviewing that chapter
may be useful if the treatment here feels too terse.)
A binary relation on a type X is a family of propositions parameterized
by two elements of X — i.e., a proposition about pairs of elements of
X.
Should we be getting this (and Deterministic, Multi, etc.
if appropriate) from the Lean standard library? If not, should we match the
concepts in CSLib, if they exists there?
def Relation (X : Type) := X → X → Prop
Our main examples of such relations in this chapter will be the
single-step reduction relation, ⟶, and its multi-step variant, ⟶*,
defined below, but there are many other examples — e.g., the "equals,"
"less than," "less than or equal to," and "is the square of" relations on
numbers, and the "prefix of" relation on lists and strings.
One simple property a relation may have is being deterministic: like Slang's big-step evaluation, each element is related to at most one other.
Theorem: For each t, there is at most one t' such that t steps to
t'. We prove it by induction on the derivation of the first step.
Proof sketch: We show that if x steps to both y₁ and y₂, then y₁
and y₂ are equal, by induction on a derivation of x ⟶ y₁. There are
several cases, depending on the last rule used in this derivation and the
last rule in the given derivation of x ⟶ y₂.
-
If both are
plus, the result is immediate. -
The cases when both derivations end with
plusLeftorplusRightfollow by the induction hypothesis. -
It cannot happen that one is
plusand the other isplusLeft/plusRight, since this would imply thatxhas the formp t₁ t₂where botht₁andt₂are constants (byplus) and one oft₁ort₂has the formp _. -
Similarly, it cannot happen that one is
plusLeftand the other isplusRight, since this would imply thatxhas the formp t₁ t₂wheret₁has both the formp t₁₁ t₁₂and the formc n.
Formally,
def Deterministic {X : Type} (R : Relation X) : Prop :=
∀ x y₁ y₂ : X, R x y₁ → R x y₂ → y₁ = y₂
namespace SimpleArith2
theorem step_deterministic : Deterministic SimpleArith1.Step := ⊢ Deterministic SimpleArith1.Step
x:Tmy₁:Tmy₂:Tmh₁:SimpleArith1.Step x y₁⊢ SimpleArith1.Step x y₂ → y₁ = y₂
induction h₁ generalizing y₂ with
x:Tmy₁:Tmn₁:Natn₂:Naty₂:Tm⊢ SimpleArith1.Step ((Tm.c n₁).p (Tm.c n₂)) y₂ → Tm.c (n₁ + n₂) = y₂
x:Tmy₁:Tmn₁:Natn₂:Naty₂:Tmh₂:SimpleArith1.Step ((Tm.c n₁).p (Tm.c n₂)) y₂⊢ Tm.c (n₁ + n₂) = y₂
x:Tmy₁:Tmn₁:Natn₂:Nat⊢ Tm.c (n₁ + n₂) = Tm.c (n₁ + n₂)x:Tmy₁:Tmn₁:Natn₂:Natt₁'✝:Tmh✝:SimpleArith1.Step (Tm.c n₁) t₁'✝⊢ Tm.c (n₁ + n₂) = t₁'✝.p (Tm.c n₂)x:Tmy₁:Tmn₁:Natn₂:Natt₂'✝:Tmh✝:SimpleArith1.Step (Tm.c n₂) t₂'✝⊢ Tm.c (n₁ + n₂) = (Tm.c n₁).p t₂'✝ x:Tmy₁:Tmn₁:Natn₂:Nat⊢ Tm.c (n₁ + n₂) = Tm.c (n₁ + n₂)x:Tmy₁:Tmn₁:Natn₂:Natt₁'✝:Tmh✝:SimpleArith1.Step (Tm.c n₁) t₁'✝⊢ Tm.c (n₁ + n₂) = t₁'✝.p (Tm.c n₂)x:Tmy₁:Tmn₁:Natn₂:Natt₂'✝:Tmh✝:SimpleArith1.Step (Tm.c n₂) t₂'✝⊢ Tm.c (n₁ + n₂) = (Tm.c n₁).p t₂'✝ first | x:Tmy₁:Tmn₁:Natn₂:Natt₂'✝:Tmh✝:SimpleArith1.Step (Tm.c n₂) t₂'✝⊢ Tm.c (n₁ + n₂) = (Tm.c n₁).p t₂'✝ | All goals completed! 🐙
x:Tmy₁:Tmt₁:Tmt₁':Tmt₂:Tmhs:SimpleArith1.Step t₁ t₁'ih:∀ (y₂ : Tm), SimpleArith1.Step t₁ y₂ → t₁' = y₂y₂:Tm⊢ SimpleArith1.Step (t₁.p t₂) y₂ → t₁'.p t₂ = y₂
x:Tmy₁:Tmt₁:Tmt₁':Tmt₂:Tmhs:SimpleArith1.Step t₁ t₁'ih:∀ (y₂ : Tm), SimpleArith1.Step t₁ y₂ → t₁' = y₂y₂:Tmh₂:SimpleArith1.Step (t₁.p t₂) y₂⊢ t₁'.p t₂ = y₂
x:Tmy₁:Tmt₁':Tmn₁✝:Natn₂✝:Naths:SimpleArith1.Step (Tm.c n₁✝) t₁'ih:∀ (y₂ : Tm), SimpleArith1.Step (Tm.c n₁✝) y₂ → t₁' = y₂⊢ t₁'.p (Tm.c n₂✝) = Tm.c (n₁✝ + n₂✝)x:Tmy₁:Tmt₁:Tmt₁':Tmt₂:Tmhs:SimpleArith1.Step t₁ t₁'ih:∀ (y₂ : Tm), SimpleArith1.Step t₁ y₂ → t₁' = y₂t₁'✝:Tmh✝:SimpleArith1.Step t₁ t₁'✝⊢ t₁'.p t₂ = t₁'✝.p t₂x:Tmy₁:Tmt₁':Tmt₂:Tmn₁✝:Natt₂'✝:Tmhs:SimpleArith1.Step (Tm.c n₁✝) t₁'ih:∀ (y₂ : Tm), SimpleArith1.Step (Tm.c n₁✝) y₂ → t₁' = y₂h✝:SimpleArith1.Step t₂ t₂'✝⊢ t₁'.p t₂ = (Tm.c n₁✝).p t₂'✝ x:Tmy₁:Tmt₁':Tmn₁✝:Natn₂✝:Naths:SimpleArith1.Step (Tm.c n₁✝) t₁'ih:∀ (y₂ : Tm), SimpleArith1.Step (Tm.c n₁✝) y₂ → t₁' = y₂⊢ t₁'.p (Tm.c n₂✝) = Tm.c (n₁✝ + n₂✝)x:Tmy₁:Tmt₁:Tmt₁':Tmt₂:Tmhs:SimpleArith1.Step t₁ t₁'ih:∀ (y₂ : Tm), SimpleArith1.Step t₁ y₂ → t₁' = y₂t₁'✝:Tmh✝:SimpleArith1.Step t₁ t₁'✝⊢ t₁'.p t₂ = t₁'✝.p t₂x:Tmy₁:Tmt₁':Tmt₂:Tmn₁✝:Natt₂'✝:Tmhs:SimpleArith1.Step (Tm.c n₁✝) t₁'ih:∀ (y₂ : Tm), SimpleArith1.Step (Tm.c n₁✝) y₂ → t₁' = y₂h✝:SimpleArith1.Step t₂ t₂'✝⊢ t₁'.p t₂ = (Tm.c n₁✝).p t₂'✝ first | All goals completed! 🐙 | All goals completed! 🐙
| plusRight n₁ t₂ t₂' hs ih => plusRight x:Tmy₁:Tmn₁:Natt₂:Tmt₂':Tmhs:SimpleArith1.Step t₂ t₂'ih:∀ (y₂ : Tm), SimpleArith1.Step t₂ y₂ → t₂' = y₂y₂:Tm⊢ SimpleArith1.Step ((Tm.c n₁).p t₂) y₂ → (Tm.c n₁).p t₂' = y₂
intro h₂ plusRight x:Tmy₁:Tmn₁:Natt₂:Tmt₂':Tmhs:SimpleArith1.Step t₂ t₂'ih:∀ (y₂ : Tm), SimpleArith1.Step t₂ y₂ → t₂' = y₂y₂:Tmh₂:SimpleArith1.Step ((Tm.c n₁).p t₂) y₂⊢ (Tm.c n₁).p t₂' = y₂
cases h₂ plusRight.plus x:Tmy₁:Tmn₁:Natt₂':Tmn₂✝:Naths:SimpleArith1.Step (Tm.c n₂✝) t₂'ih:∀ (y₂ : Tm), SimpleArith1.Step (Tm.c n₂✝) y₂ → t₂' = y₂⊢ (Tm.c n₁).p t₂' = Tm.c (n₁ + n₂✝)plusRight.plusLeft x:Tmy₁:Tmn₁:Natt₂:Tmt₂':Tmhs:SimpleArith1.Step t₂ t₂'ih:∀ (y₂ : Tm), SimpleArith1.Step t₂ y₂ → t₂' = y₂t₁'✝:Tmh✝:SimpleArith1.Step (Tm.c n₁) t₁'✝⊢ (Tm.c n₁).p t₂' = t₁'✝.p t₂plusRight.plusRight x:Tmy₁:Tmn₁:Natt₂:Tmt₂':Tmhs:SimpleArith1.Step t₂ t₂'ih:∀ (y₂ : Tm), SimpleArith1.Step t₂ y₂ → t₂' = y₂t₂'✝:Tmh✝:SimpleArith1.Step t₂ t₂'✝⊢ (Tm.c n₁).p t₂' = (Tm.c n₁).p t₂'✝ <;> plusRight.plus x:Tmy₁:Tmn₁:Natt₂':Tmn₂✝:Naths:SimpleArith1.Step (Tm.c n₂✝) t₂'ih:∀ (y₂ : Tm), SimpleArith1.Step (Tm.c n₂✝) y₂ → t₂' = y₂⊢ (Tm.c n₁).p t₂' = Tm.c (n₁ + n₂✝)plusRight.plusLeft x:Tmy₁:Tmn₁:Natt₂:Tmt₂':Tmhs:SimpleArith1.Step t₂ t₂'ih:∀ (y₂ : Tm), SimpleArith1.Step t₂ y₂ → t₂' = y₂t₁'✝:Tmh✝:SimpleArith1.Step (Tm.c n₁) t₁'✝⊢ (Tm.c n₁).p t₂' = t₁'✝.p t₂plusRight.plusRight x:Tmy₁:Tmn₁:Natt₂:Tmt₂':Tmhs:SimpleArith1.Step t₂ t₂'ih:∀ (y₂ : Tm), SimpleArith1.Step t₂ y₂ → t₂' = y₂t₂'✝:Tmh✝:SimpleArith1.Step t₂ t₂'✝⊢ (Tm.c n₁).p t₂' = (Tm.c n₁).p t₂'✝ first | cases ‹SimpleArith1.Step (.c _) _› plusRight.plusRight x:Tmy₁:Tmn₁:Natt₂:Tmt₂':Tmhs:SimpleArith1.Step t₂ t₂'ih:∀ (y₂ : Tm), SimpleArith1.Step t₂ y₂ → t₂' = y₂t₂'✝:Tmh✝:SimpleArith1.Step t₂ t₂'✝⊢ (Tm.c n₁).p t₂' = (Tm.c n₁).p t₂'✝ | rw [ih _ ‹SimpleArith1.Step t₂ _› plusRight.plusRight x:Tmy₁:Tmn₁:Natt₂:Tmt₂':Tmhs:SimpleArith1.Step t₂ t₂'ih:∀ (y₂ : Tm), SimpleArith1.Step t₂ y₂ → t₂' = y₂t₂'✝:Tmh✝:SimpleArith1.Step t₂ t₂'✝⊢ (Tm.c n₁).p t₂'✝ = (Tm.c n₁).p t₂'✝] All goals completed! 🐙
end SimpleArith2
In the Rocq there is the development of a special tactic to make this proof simpler. Do we want that here?
3.3.1. Values
Next, it will be useful to slightly reformulate the definition of single-step reduction by stating it in terms of "values."
It can be useful to think of the ⟶ relation as defining an abstract
machine:
-
At any moment, the state of the machine is a term.
-
A step of the machine is an atomic unit of computation — here, a single "add" operation.
-
The halting states of the machine are ones where there is no more computation to be done.
We can then execute a term t as follows:
-
Take
tas the starting state of the machine. -
Repeatedly use the
⟶relation to find a sequence of machine states, starting witht, where each state steps to the next. -
When no more reduction is possible, "read out" the final state of the machine as the result of execution.
Intuitively, it is clear that the final states of our machine are always
terms of the form c n for some n. We call such terms values.
inductive IsValue : Tm → Prop where
| const (n : Nat) : IsValue (.c n)
Having introduced the idea of values, we can use it in the definition of
the ⟶ relation to write the plusRight rule in a slightly more elegant way.
------------------------------ (plus)
p (c n₁) (c n₂) ⟶ c (n₁ + n₂)
t₁ ⟶ t₁'
------------------- (plusLeft)
p t₁ t₂ ⟶ p t₁' t₂
IsValue v₁
t₂ ⟶ t₂'
------------------- (plusRight)
p v₁ t₂ ⟶ p v₁ t₂'
Again, the variable names in the informal presentation carry important
information: by convention, v₁ ranges only over values, while t₁ and
t₂ range over arbitrary terms.
(Given this convention, the explicit IsValue hypothesis is arguably
redundant, since the naming convention tells us where to add it when
translating the informal rule to Lean. We'll keep it for now, to maintain
a close correspondence between the informal and Lean versions of the rules,
but later on we'll drop it in informal rules for brevity.)
Here are the formal rules.
inductive Step : Tm → Tm → Prop where
| plus (n₁ n₂ : Nat) :
Step (.p (.c n₁) (.c n₂)) (.c (n₁ + n₂))
| plusLeft (t₁ t₁' t₂ : Tm)
(h : Step t₁ t₁') :
Step (.p t₁ t₂) (.p t₁' t₂)
| plusRight (v₁ t₂ t₂' : Tm)
(hv : IsValue v₁)
(h : Step t₂ t₂') :
Step (.p v₁ t₂) (.p v₁ t₂')
notation:40 t:41 " ⟶ " t':41 => Step t t'
As a sanity check on this change, let's re-verify determinism. Here's an informal proof:
Proof sketch: We must show that if x steps to both y₁ and y₂, then
y₁ and y₂ are equal. Consider the final rules used in the derivations
of x ⟶ y₁ and x ⟶ y₂.
-
If both are
plus, the result is immediate. -
The cases when both derivations end with
plusLeftorplusRightfollow by the induction hypothesis. -
It cannot happen that one is
plusand the other isplusLeft/plusRight, since this would imply thatxhas the formp t₁ t₂where botht₁andt₂are constants (byplus) and one oft₁ort₂has the formp _. -
Similarly, it cannot happen that one is
plusLeftand the other isplusRight, since this would imply thatxhas the formp t₁ t₂wheret₁both has the formp t₁₁ t₁₂and is a value (hence has the formc n).
Most of this proof is the same as the one above. But to get maximum
benefit from the exercise you should try to write your formal version from
scratch and just use the earlier one if you get stuck. The impossible
cross-cases now also use the fact that a IsValue (a c n) cannot step.
theorem step_deterministic : Deterministic Step := by ⊢ Deterministic Step
solution!
intro x y₁ y₂ h₁ x:Tmy₁:Tmy₂:Tmh₁:x ⟶ y₁⊢ x ⟶ y₂ → y₁ = y₂
induction h₁ generalizing y₂ with
| plus n₁ n₂ => plus x:Tmy₁:Tmn₁:Natn₂:Naty₂:Tm⊢ (Tm.c n₁).p (Tm.c n₂) ⟶ y₂ → Tm.c (n₁ + n₂) = y₂
intro h₂ plus x:Tmy₁:Tmn₁:Natn₂:Naty₂:Tmh₂:(Tm.c n₁).p (Tm.c n₂) ⟶ y₂⊢ Tm.c (n₁ + n₂) = y₂; cases h₂ with
| plus => plus.plus x:Tmy₁:Tmn₁:Natn₂:Nat⊢ Tm.c (n₁ + n₂) = Tm.c (n₁ + n₂) rfl All goals completed! 🐙
| plusLeft _ _ _ hs => plus.plusLeft x:Tmy₁:Tmn₁:Natn₂:Natt₁'✝:Tmhs:Tm.c n₁ ⟶ t₁'✝⊢ Tm.c (n₁ + n₂) = t₁'✝.p (Tm.c n₂) cases hs All goals completed! 🐙
| plusRight _ _ _ _ hs => plus.plusRight x:Tmy₁:Tmn₁:Natn₂:Natt₂'✝:Tmhv✝:IsValue (Tm.c n₁)hs:Tm.c n₂ ⟶ t₂'✝⊢ Tm.c (n₁ + n₂) = (Tm.c n₁).p t₂'✝ cases hs All goals completed! 🐙
| plusLeft t₁ t₁' t₂ hs ih => plusLeft x:Tmy₁:Tmt₁:Tmt₁':Tmt₂:Tmhs:t₁ ⟶ t₁'ih:∀ (y₂ : Tm), t₁ ⟶ y₂ → t₁' = y₂y₂:Tm⊢ t₁.p t₂ ⟶ y₂ → t₁'.p t₂ = y₂
intro h₂ plusLeft x:Tmy₁:Tmt₁:Tmt₁':Tmt₂:Tmhs:t₁ ⟶ t₁'ih:∀ (y₂ : Tm), t₁ ⟶ y₂ → t₁' = y₂y₂:Tmh₂:t₁.p t₂ ⟶ y₂⊢ t₁'.p t₂ = y₂; cases h₂ with
| plus => plusLeft.plus x:Tmy₁:Tmt₁':Tmn₁✝:Natn₂✝:Naths:Tm.c n₁✝ ⟶ t₁'ih:∀ (y₂ : Tm), Tm.c n₁✝ ⟶ y₂ → t₁' = y₂⊢ t₁'.p (Tm.c n₂✝) = Tm.c (n₁✝ + n₂✝) cases hs All goals completed! 🐙
| plusLeft _ _ _ hs₂ => plusLeft.plusLeft x:Tmy₁:Tmt₁:Tmt₁':Tmt₂:Tmhs:t₁ ⟶ t₁'ih:∀ (y₂ : Tm), t₁ ⟶ y₂ → t₁' = y₂t₁'✝:Tmhs₂:t₁ ⟶ t₁'✝⊢ t₁'.p t₂ = t₁'✝.p t₂ rw [ih _ hs₂ plusLeft.plusLeft x:Tmy₁:Tmt₁:Tmt₁':Tmt₂:Tmhs:t₁ ⟶ t₁'ih:∀ (y₂ : Tm), t₁ ⟶ y₂ → t₁' = y₂t₁'✝:Tmhs₂:t₁ ⟶ t₁'✝⊢ t₁'✝.p t₂ = t₁'✝.p t₂] All goals completed! 🐙
| plusRight _ _ _ hv hs₂ => plusLeft.plusRight x:Tmy₁:Tmt₁:Tmt₁':Tmt₂:Tmhs:t₁ ⟶ t₁'ih:∀ (y₂ : Tm), t₁ ⟶ y₂ → t₁' = y₂t₂'✝:Tmhv:IsValue t₁hs₂:t₂ ⟶ t₂'✝⊢ t₁'.p t₂ = t₁.p t₂'✝ cases hv plusLeft.plusRight.const x:Tmy₁:Tmt₁':Tmt₂:Tmt₂'✝:Tmhs₂:t₂ ⟶ t₂'✝n✝:Naths:Tm.c n✝ ⟶ t₁'ih:∀ (y₂ : Tm), Tm.c n✝ ⟶ y₂ → t₁' = y₂⊢ t₁'.p t₂ = (Tm.c n✝).p t₂'✝; cases hs All goals completed! 🐙
| plusRight v₁ t₂ t₂' hv hs ih => plusRight x:Tmy₁:Tmv₁:Tmt₂:Tmt₂':Tmhv:IsValue v₁hs:t₂ ⟶ t₂'ih:∀ (y₂ : Tm), t₂ ⟶ y₂ → t₂' = y₂y₂:Tm⊢ v₁.p t₂ ⟶ y₂ → v₁.p t₂' = y₂
intro h₂ plusRight x:Tmy₁:Tmv₁:Tmt₂:Tmt₂':Tmhv:IsValue v₁hs:t₂ ⟶ t₂'ih:∀ (y₂ : Tm), t₂ ⟶ y₂ → t₂' = y₂y₂:Tmh₂:v₁.p t₂ ⟶ y₂⊢ v₁.p t₂' = y₂; cases h₂ with
| plus => plusRight.plus x:Tmy₁:Tmt₂':Tmn₁✝:Natn₂✝:Nathv:IsValue (Tm.c n₁✝)hs:Tm.c n₂✝ ⟶ t₂'ih:∀ (y₂ : Tm), Tm.c n₂✝ ⟶ y₂ → t₂' = y₂⊢ (Tm.c n₁✝).p t₂' = Tm.c (n₁✝ + n₂✝) cases hs All goals completed! 🐙
| plusLeft _ _ _ hs₂ => plusRight.plusLeft x:Tmy₁:Tmv₁:Tmt₂:Tmt₂':Tmhv:IsValue v₁hs:t₂ ⟶ t₂'ih:∀ (y₂ : Tm), t₂ ⟶ y₂ → t₂' = y₂t₁'✝:Tmhs₂:v₁ ⟶ t₁'✝⊢ v₁.p t₂' = t₁'✝.p t₂ cases hv plusRight.plusLeft.const x:Tmy₁:Tmt₂:Tmt₂':Tmhs:t₂ ⟶ t₂'ih:∀ (y₂ : Tm), t₂ ⟶ y₂ → t₂' = y₂t₁'✝:Tmn✝:Naths₂:Tm.c n✝ ⟶ t₁'✝⊢ (Tm.c n✝).p t₂' = t₁'✝.p t₂; cases hs₂ All goals completed! 🐙
| plusRight _ _ _ _ hs₂ => plusRight.plusRight x:Tmy₁:Tmv₁:Tmt₂:Tmt₂':Tmhv:IsValue v₁hs:t₂ ⟶ t₂'ih:∀ (y₂ : Tm), t₂ ⟶ y₂ → t₂' = y₂t₂'✝:Tmhv✝:IsValue v₁hs₂:t₂ ⟶ t₂'✝⊢ v₁.p t₂' = v₁.p t₂'✝ rw [ih _ hs₂ plusRight.plusRight x:Tmy₁:Tmv₁:Tmt₂:Tmt₂':Tmhv:IsValue v₁hs:t₂ ⟶ t₂'ih:∀ (y₂ : Tm), t₂ ⟶ y₂ → t₂' = y₂t₂'✝:Tmhv✝:IsValue v₁hs₂:t₂ ⟶ t₂'✝⊢ v₁.p t₂'✝ = v₁.p t₂'✝] All goals completed! 🐙
3.3.2. Strong Progress and Normal Forms
The definition of single-step reduction for our toy language is fairly simple, but for a larger language it would be easy to forget one of the rules and accidentally create a situation where some term cannot take a step even though it has not been completely reduced to a value. The following theorem shows that we did not, in fact, make such a mistake here.
Theorem (Strong Progress): If t is a term, then either t is a value
or else there exists a term t' such that t ⟶ t'.
Proof: By induction on t.
-
Suppose
t = c n. Thentis a value. -
Suppose
t = p t₁ t₂, where (by the IH)t₁either is a value or can step to somet₁', and wheret₂is either a value or can step to somet₂'. We must showp t₁ t₂is either a value or steps to somet'.-
If
t₁andt₂are both values, thentcan take a step, byplus. -
If
t₁is a value andt₂can take a step, then so cant, byplusRight. -
If
t₁can take a step, then so cant, byplusLeft.
-
Or, formally:
theorem strong_progress (t : Tm) : IsValue t ∨ ∃ t', t ⟶ t' := by t:Tm⊢ IsValue t ∨ ∃ t', t ⟶ t'
induction t with
| c n => c n:Nat⊢ IsValue (Tm.c n) ∨ ∃ t', Tm.c n ⟶ t' left c n:Nat⊢ IsValue (Tm.c n); exact .const n All goals completed! 🐙
| p t₁ t₂ ih₁ ih₂ => p t₁:Tmt₂:Tmih₁:IsValue t₁ ∨ ∃ t', t₁ ⟶ t'ih₂:IsValue t₂ ∨ ∃ t', t₂ ⟶ t'⊢ IsValue (t₁.p t₂) ∨ ∃ t', t₁.p t₂ ⟶ t'
right p t₁:Tmt₂:Tmih₁:IsValue t₁ ∨ ∃ t', t₁ ⟶ t'ih₂:IsValue t₂ ∨ ∃ t', t₂ ⟶ t'⊢ ∃ t', t₁.p t₂ ⟶ t'
cases ih₁ with
| inl hv₁ => p.inl t₁:Tmt₂:Tmih₂:IsValue t₂ ∨ ∃ t', t₂ ⟶ t'hv₁:IsValue t₁⊢ ∃ t', t₁.p t₂ ⟶ t'
cases ih₂ with
| inl hv₂ => p.inl.inl t₁:Tmt₂:Tmhv₁:IsValue t₁hv₂:IsValue t₂⊢ ∃ t', t₁.p t₂ ⟶ t'
cases hv₁ with
| const n₁ => p.inl.inl.const t₂:Tmhv₂:IsValue t₂n₁:Nat⊢ ∃ t', (Tm.c n₁).p t₂ ⟶ t'
cases hv₂ with
| const n₂ => p.inl.inl.const.const n₁:Natn₂:Nat⊢ ∃ t', (Tm.c n₁).p (Tm.c n₂) ⟶ t' exact ⟨.c (n₁ + n₂), .plus n₁ n₂⟩ All goals completed! 🐙
| inr h₂ => p.inl.inr t₁:Tmt₂:Tmhv₁:IsValue t₁h₂:∃ t', t₂ ⟶ t'⊢ ∃ t', t₁.p t₂ ⟶ t'
obtain ⟨t₂', ht₂⟩ := h₂ p.inl.inr t₁:Tmt₂:Tmhv₁:IsValue t₁t₂':Tmht₂:t₂ ⟶ t₂'⊢ ∃ t', t₁.p t₂ ⟶ t'
exact ⟨.p t₁ t₂', .plusRight t₁ t₂ t₂' hv₁ ht₂⟩ All goals completed! 🐙
| inr h₁ => p.inr t₁:Tmt₂:Tmih₂:IsValue t₂ ∨ ∃ t', t₂ ⟶ t'h₁:∃ t', t₁ ⟶ t'⊢ ∃ t', t₁.p t₂ ⟶ t'
obtain ⟨t₁', ht₁⟩ := h₁ p.inr t₁:Tmt₂:Tmih₂:IsValue t₂ ∨ ∃ t', t₂ ⟶ t't₁':Tmht₁:t₁ ⟶ t₁'⊢ ∃ t', t₁.p t₂ ⟶ t'
exact ⟨.p t₁' t₂, .plusLeft t₁ t₁' t₂ ht₁⟩ All goals completed! 🐙
This important property is called strong progress, because every term either is a value or can "make progress" by stepping to some other term. (The qualifier "strong" distinguishes it from a more refined version that we'll see in later chapters, called simply progress.)
The idea of "making progress" can be extended to tell us something interesting about values in this language: they are exactly the terms that do not make progress in this sense. Let's give a name to "terms that cannot make progress." We'll call them normal forms.
def IsNormalForm {X : Type} (R : Relation X) (t : X) : Prop :=
¬ ∃ t', R t t'
Note that this definition specifies what it is to be a normal form for an
arbitrary relation R over an arbitrary type X, not just for the
particular single-step reduction relation over terms that we are interested
in at the moment. We'll re-use the same terminology for talking about
other relations later in the course.
We can use this terminology to generalize the observation we made in the strong progress theorem: in this language (though not necessarily, in general), normal forms and values are actually the same thing.
theorem value_is_nf (v : Tm) (h : IsValue v) : IsNormalForm Step v := by v:Tmh:IsValue v⊢ IsNormalForm Step v
intro hc v:Tmh:IsValue vhc:∃ t', v ⟶ t'⊢ False
obtain ⟨t', ht⟩ := hc v:Tmh:IsValue vt':Tmht:v ⟶ t'⊢ False
cases h with
| const n => const t':Tmn:Natht:Tm.c n ⟶ t'⊢ False cases ht All goals completed! 🐙
theorem nf_is_value (t : Tm) (h : IsNormalForm Step t) : IsValue t := by t:Tmh:IsNormalForm Step t⊢ IsValue t
cases strong_progress t with
| inl hv => inl t:Tmh:IsNormalForm Step thv:IsValue t⊢ IsValue t exact hv All goals completed! 🐙
| inr hstep => inr t:Tmh:IsNormalForm Step thstep:∃ t', t ⟶ t'⊢ IsValue t exact absurd hstep h All goals completed! 🐙
theorem nf_same_as_value (t : Tm) : IsNormalForm Step t ↔ IsValue t :=
⟨nf_is_value t, value_is_nf t⟩
Tactic absurd is first introduced here. Do we want to explain it?
I think some of these proofs were originally Claude-generated, so we probably want to redo them from scratch, in which case introducing absurd is likely not necessary.
Why is this interesting? Because IsValue is a syntactic concept — it is
defined by looking at the way a term is written — while IsNormalForm is a
semantic one — it is defined by looking at how the term steps.
It is not obvious that these concepts should characterize the same set of terms!
Indeed, we could easily have written the definitions (incorrectly) so that they would not coincide.
Suppose, for example, we define IsValue so that it includes some terms that
are not finished reducing. (Even if you don't work the exercise
value_not_same_as_normal_form1 below and the following ones, make sure you
can think of an example of such a term.)
namespace Temp1
inductive IsValue : Tm → Prop where
| const (n : Nat) : IsValue (.c n)
| funny (t₁ : Tm) (n : Nat) : IsValue (.p t₁ (.c n)) -- <---
inductive Step : Tm → Tm → Prop where
| plus (n₁ n₂ : Nat) : Step (.p (.c n₁) (.c n₂)) (.c (n₁ + n₂))
| plusLeft (t₁ t₁' t₂ : Tm) (h : Step t₁ t₁') : Step (.p t₁ t₂) (.p t₁' t₂)
| plusRight (v₁ t₂ t₂' : Tm) (hv : IsValue v₁) (h : Step t₂ t₂') : Step (.p v₁ t₂) (.p v₁ t₂')
Using this wrong definition of IsValue, to how many different values does
the following term reduce in zero or more steps?
.p (.p (.c 1) (.c 2)) (.c 3)
Show solution
Three: `.p (.p (.c 1) (.c 2)) (.c 3)` itself is a value; `.p (.c 3) (.c 3)` is a value; `.c 6` is a value.
To how many different terms does the following term Step (in one step)?
.p (.p (.c 1) (.c 2)) (.p (.c 3) (.c 4))
Show solution
Two: `.p (.c 3) (.p (.c 3) (.c 4))` via `plusLeft` and `.p (.p (.c 1) (.c 2)) (.c 7)` via `plusRight`.
theorem value_not_same_as_normal_form :
∃ v, IsValue v ∧ ¬ IsNormalForm Step v := by ⊢ ∃ v, IsValue v ∧ ¬IsNormalForm Step v
apply Exists.intro (.p (.c 0) (.c 0)) ⊢ IsValue ((Tm.c 0).p (Tm.c 0)) ∧ ¬IsNormalForm Step ((Tm.c 0).p (Tm.c 0))
apply And.intro (.funny _ 0) ⊢ ¬IsNormalForm Step ((Tm.c 0).p (Tm.c 0))
solution!
intro h h:IsNormalForm Step ((Tm.c 0).p (Tm.c 0))⊢ False
exact h ⟨.c (0 + 0), .plus 0 0⟩ All goals completed! 🐙
end Temp1
Or we might (again, wrongly) define Step so that it permits something
designated as a value to reduce further. We again lose the property that
values are the same as normal forms.
namespace Temp2
inductive IsValue : Tm → Prop where
| const (n : Nat) : IsValue (.c n) -- Original definition
inductive Step : Tm → Tm → Prop where
| funny (n : Nat) : Step (.c n) (.p (.c n) (.c 0)) -- <--- NEW
| plus (n₁ n₂ : Nat) : Step (.p (.c n₁) (.c n₂)) (.c (n₁ + n₂))
| plusLeft (t₁ t₁' t₂ : Tm) (h : Step t₁ t₁') : Step (.p t₁ t₂) (.p t₁' t₂)
| plusRight (v₁ t₂ t₂' : Tm) (hv : IsValue v₁) (h : Step t₂ t₂') : Step (.p v₁ t₂) (.p v₁ t₂')
With this definition, to how many different terms does the following term step (in exactly one step)?
.p (.c 1) (.c 3)
Show solution
Three: `plus` yields `.c 4`; `plusLeft` with `funny` yields `.p (.p (.c 1) (.c 0)) (.c 3)`; `plusRight` with `funny` yields `.p (.c 1) (.p (.c 3) (.c 0))`.
theorem value_not_same_as_normal_form :
∃ v, IsValue v ∧ ¬ IsNormalForm Step v := by ⊢ ∃ v, IsValue v ∧ ¬IsNormalForm Step v
apply Exists.intro (.c 5) ⊢ IsValue (Tm.c 5) ∧ ¬IsNormalForm Step (Tm.c 5)
apply And.intro (.const 5) ⊢ ¬IsNormalForm Step (Tm.c 5)
solution!
intro h h:IsNormalForm Step (Tm.c 5)⊢ False
exact h ⟨.p (.c 5) (.c 0), .funny 5⟩ All goals completed! 🐙
end Temp2
Finally, we might define IsValue and Step so that there is some term that
is not a value but that also cannot take a step. Such terms are said to
be stuck. In this case, this is caused by a mistake in the semantics, but
we will also see situations where, even in a correct language definition, it
makes sense to allow some terms to be stuck. (Note that plusRight is missing
below.)
namespace Temp3
inductive IsValue : Tm → Prop where
| const (n : Nat) : IsValue (.c n)
inductive Step : Tm → Tm → Prop where
| plus (n₁ n₂ : Nat) : Step (.p (.c n₁) (.c n₂)) (.c (n₁ + n₂))
| plusLeft (t₁ t₁' t₂ : Tm) (h : Step t₁ t₁') : Step (.p t₁ t₂) (.p t₁' t₂)
With this definition, to how many terms does the following term step (in one step)?
.p (.c 1) (.p (.c 1) (.c 2))
Show solution
none!
theorem value_not_same_as_normal_form :
∃ t, ¬ IsValue t ∧ IsNormalForm Step t := by ⊢ ∃ t, ¬IsValue t ∧ IsNormalForm Step t
apply Exists.intro (.p (.c 1) (.p (.c 1) (.c 2))) ⊢ ¬IsValue ((Tm.c 1).p ((Tm.c 1).p (Tm.c 2))) ∧ IsNormalForm Step ((Tm.c 1).p ((Tm.c 1).p (Tm.c 2)))
apply And.intro left ⊢ ¬IsValue ((Tm.c 1).p ((Tm.c 1).p (Tm.c 2)))right ⊢ IsNormalForm Step ((Tm.c 1).p ((Tm.c 1).p (Tm.c 2)))
· left ⊢ ¬IsValue ((Tm.c 1).p ((Tm.c 1).p (Tm.c 2))) solution!
intro h left h:IsValue ((Tm.c 1).p ((Tm.c 1).p (Tm.c 2)))⊢ False; cases h All goals completed! 🐙
· right ⊢ IsNormalForm Step ((Tm.c 1).p ((Tm.c 1).p (Tm.c 2))) solution!
intro h right h:∃ t', Step ((Tm.c 1).p ((Tm.c 1).p (Tm.c 2))) t'⊢ False
obtain ⟨t', ht⟩ := h right t':Tmht:Step ((Tm.c 1).p ((Tm.c 1).p (Tm.c 2))) t'⊢ False
cases ht with
| plusLeft _ _ _ hs => right.plusLeft t₁'✝:Tmhs:Step (Tm.c 1) t₁'✝⊢ False cases hs All goals completed! 🐙
end Temp3
3.4. Multi-Step Reduction
We've been working so far with the single-step reduction relation ⟶,
which formalizes the individual steps of an abstract machine for executing
programs. We can use the same machine to reduce programs to completion —
to find out what final result they yield. This can be formalized as
follows:
-
First, we define a multi-step reduction relation
⟶*, which relates termstandt'iftcan reacht'by any number (including zero) of single reduction steps. -
Then we define a "result" of a term
tas a normal form thattcan reach by multi-step reduction.
Since we'll want to reuse the idea of multi-step reduction many times with
many different single-step relations, let's define the concept generically.
Given a relation R (e.g., the step relation ⟶), we define a new relation
Multi R, called the multi-step closure of R, as follows.
inductive Multi {X : Type} (R : Relation X) : X → X → Prop where
| refl (x : X) : Multi R x x
| step (x y z : X) (h₁ : R x y) (h₂ : Multi R y z) : Multi R x z
I would make some arguments implicit to proivde a cleaner interface (FYI the mathlib version)
The effect of this definition is that Multi R relates two elements x and y if
-
x = y, or -
R x y, or -
there is some nonempty sequence
z₁,z₂, ...,zₙsuch thatR x₁ z₁, R z₁ z₂, ..., R zₙ y.
Intuitively, if R describes a single-step of computation, then z₁ ... zₙ are the intermediate steps of computation that get us from x to y.
We write ⟶* for the Multi Step relation on terms
notation:40 t:41 " ⟶* " t':41 => Multi Step t t'
The relation Multi R has several crucial properties.
First, it is obviously reflexive (a term can execute to itself by taking zero
steps). That is just what the Multi.refl constructor says, so such a goal can
always be closed with exact .refl _. It comes up often enough that it is
worth registering the constructor as a reflexivity lemma, with the @[refl]
attribute. The rfl tactic then closes a zero-step execution exactly as it
closes x = x:
attribute [refl] Multi.refl
example : (.c 5 : Tm) ⟶* .c 5 := by ⊢ Tm.c 5 ⟶* Tm.c 5 rfl All goals completed! 🐙
This pays off at the end of a reduction sequence too: the final Multi.step
leaves a goal relating a term to itself, which rfl discharges.
example : (.p (.c 1) (.c 2)) ⟶* .c (1 + 2) := by ⊢ (Tm.c 1).p (Tm.c 2) ⟶* Tm.c (1 + 2)
apply Multi.step (y := .c (1 + 2)) h₁ ⊢ (Tm.c 1).p (Tm.c 2) ⟶ Tm.c (1 + 2)h₂ ⊢ Tm.c (1 + 2) ⟶* Tm.c (1 + 2)
· h₁ ⊢ (Tm.c 1).p (Tm.c 2) ⟶ Tm.c (1 + 2) exact .plus 1 2 All goals completed! 🐙
· h₂ ⊢ Tm.c (1 + 2) ⟶* Tm.c (1 + 2) rfl All goals completed! 🐙
Second, it contains R — single-step reductions are a particular case of
multi-step executions. (It is this fact that justifies the word "closure"
in "multi-step closure of R.")
theorem multi_single {X : Type} (R : Relation X) (x y : X) (h : R x y) :
Multi R x y :=
.step x y y h (.refl y)
Third, Multi R is transitive.
theorem multi_trans {X : Type} (R : Relation X) (x y z : X)
(g : Multi R x y) (h : Multi R y z) : Multi R x z := by X:TypeR:Relation Xx:Xy:Xz:Xg:Multi R x yh:Multi R y z⊢ Multi R x z
induction g with
| refl a => refl X:TypeR:Relation Xx:Xy:Xz:Xa:Xh:Multi R a z⊢ Multi R a z exact h All goals completed! 🐙
| step a b c h₁ h₂ ih => step X:TypeR:Relation Xx:Xy:Xz:Xa:Xb:Xc:Xh₁:R a bh₂:Multi R b cih:Multi R c z → Multi R b zh:Multi R c z⊢ Multi R a z exact .step a b z h₁ (ih h) All goals completed! 🐙
In particular, for the Multi Step relation on terms, if t₁ ⟶* t₂ and
t₂ ⟶* t₃, then t₁ ⟶* t₃.
Which of the following relations on numbers cannot be expressed as
Multi R for some R?
(A) less than or equal (B) strictly less than (C) equal (D) none of the above
Show solution
(B) strictly less than
3.4.1. Examples
example :
(.p (.p (.c 0) (.c 3)) (.p (.c 2) (.c 4))) ⟶* .c ((0 + 3) + (2 + 4)) := by ⊢ ((Tm.c 0).p (Tm.c 3)).p ((Tm.c 2).p (Tm.c 4)) ⟶* Tm.c (0 + 3 + (2 + 4))
apply Multi.step (y := .p (.c (0 + 3)) (.p (.c 2) (.c 4))) h₁ ⊢ ((Tm.c 0).p (Tm.c 3)).p ((Tm.c 2).p (Tm.c 4)) ⟶ (Tm.c (0 + 3)).p ((Tm.c 2).p (Tm.c 4))h₂ ⊢ (Tm.c (0 + 3)).p ((Tm.c 2).p (Tm.c 4)) ⟶* Tm.c (0 + 3 + (2 + 4))
· h₁ ⊢ ((Tm.c 0).p (Tm.c 3)).p ((Tm.c 2).p (Tm.c 4)) ⟶ (Tm.c (0 + 3)).p ((Tm.c 2).p (Tm.c 4)) exact .plusLeft _ _ _ (.plus 0 3) All goals completed! 🐙
apply Multi.step (y := .p (.c (0 + 3)) (.c (2 + 4))) h₂.h₁ ⊢ (Tm.c (0 + 3)).p ((Tm.c 2).p (Tm.c 4)) ⟶ (Tm.c (0 + 3)).p (Tm.c (2 + 4))h₂.h₂ ⊢ (Tm.c (0 + 3)).p (Tm.c (2 + 4)) ⟶* Tm.c (0 + 3 + (2 + 4))
· h₂.h₁ ⊢ (Tm.c (0 + 3)).p ((Tm.c 2).p (Tm.c 4)) ⟶ (Tm.c (0 + 3)).p (Tm.c (2 + 4)) exact .plusRight _ _ _ (.const _) (.plus 2 4) All goals completed! 🐙
· h₂.h₂ ⊢ (Tm.c (0 + 3)).p (Tm.c (2 + 4)) ⟶* Tm.c (0 + 3 + (2 + 4)) exact multi_single _ _ _ (.plus (0 + 3) (2 + 4)) All goals completed! 🐙
example : (.p (.c 0) (.c 3)) ⟶* .p (.c 0) (.c 3) := solution!(.refl _)
example :
(.p (.c 0) (.p (.c 2) (.p (.c 0) (.c 3))))
⟶* (.p (.c 0) (.c (2 + (0 + 3)))) := by ⊢ (Tm.c 0).p ((Tm.c 2).p ((Tm.c 0).p (Tm.c 3))) ⟶* (Tm.c 0).p (Tm.c (2 + (0 + 3)))
solution!
apply Multi.step (y := .p (.c 0) (.p (.c 2) (.c (0 + 3)))) h₁ ⊢ (Tm.c 0).p ((Tm.c 2).p ((Tm.c 0).p (Tm.c 3))) ⟶ (Tm.c 0).p ((Tm.c 2).p (Tm.c (0 + 3)))h₂ ⊢ (Tm.c 0).p ((Tm.c 2).p (Tm.c (0 + 3))) ⟶* (Tm.c 0).p (Tm.c (2 + (0 + 3)))
· h₁ ⊢ (Tm.c 0).p ((Tm.c 2).p ((Tm.c 0).p (Tm.c 3))) ⟶ (Tm.c 0).p ((Tm.c 2).p (Tm.c (0 + 3))) exact .plusRight _ _ _ (.const 0) (.plusRight _ _ _ (.const 2) (.plus 0 3)) All goals completed! 🐙
· h₂ ⊢ (Tm.c 0).p ((Tm.c 2).p (Tm.c (0 + 3))) ⟶* (Tm.c 0).p (Tm.c (2 + (0 + 3))) exact multi_single _ _ _ (.plusRight _ _ _ (.const 0) (.plus 2 (0 + 3))) All goals completed! 🐙
Prove the following reduction, ending the chain with rfl instead of
multi_single.
example : (.p (.p (.c 1) (.c 2)) (.c 4)) ⟶* .c ((1 + 2) + 4) := by ⊢ ((Tm.c 1).p (Tm.c 2)).p (Tm.c 4) ⟶* Tm.c (1 + 2 + 4)
solution!
apply Multi.step (y := .p (.c (1 + 2)) (.c 4)) h₁ ⊢ ((Tm.c 1).p (Tm.c 2)).p (Tm.c 4) ⟶ (Tm.c (1 + 2)).p (Tm.c 4)h₂ ⊢ (Tm.c (1 + 2)).p (Tm.c 4) ⟶* Tm.c (1 + 2 + 4)
· h₁ ⊢ ((Tm.c 1).p (Tm.c 2)).p (Tm.c 4) ⟶ (Tm.c (1 + 2)).p (Tm.c 4) exact .plusLeft _ _ _ (.plus 1 2) All goals completed! 🐙
apply Multi.step (y := .c ((1 + 2) + 4)) h₂.h₁ ⊢ (Tm.c (1 + 2)).p (Tm.c 4) ⟶ Tm.c (1 + 2 + 4)h₂.h₂ ⊢ Tm.c (1 + 2 + 4) ⟶* Tm.c (1 + 2 + 4)
· h₂.h₁ ⊢ (Tm.c (1 + 2)).p (Tm.c 4) ⟶ Tm.c (1 + 2 + 4) exact .plus (1 + 2) 4 All goals completed! 🐙
· h₂.h₂ ⊢ Tm.c (1 + 2 + 4) ⟶* Tm.c (1 + 2 + 4) rfl All goals completed! 🐙
3.4.2. Normal Forms Again
If t reduces to t' in zero or more steps and t' is a normal form, we
say that "t' is a normal form of t."
def IsNormalFormOf {X : Type} (R : Relation X) (t t' : X) : Prop :=
Multi R t t' ∧ IsNormalForm R t'
We have already seen that, for our language, single-step reduction is
deterministic — i.e., a given term can take a single step in at most one
way. It follows that, if t can reach a normal form, then this normal form
is unique.
In other words, we can actually pronounce IsNormalFormOf t t'
as "t' is the normal form of t."
theorem normal_forms_unique : Deterministic (IsNormalFormOf Step) := by ⊢ Deterministic (IsNormalFormOf Step)
-- We recommend using this initial setup as-is!
intro x y₁ y₂ p₁ p₂ x:Tmy₁:Tmy₂:Tmp₁:IsNormalFormOf Step x y₁p₂:IsNormalFormOf Step x y₂⊢ y₁ = y₂
obtain ⟨p₁₁, p₁₂⟩ := p₁ x:Tmy₁:Tmy₂:Tmp₂:IsNormalFormOf Step x y₂p₁₁:x ⟶* y₁p₁₂:IsNormalForm Step y₁⊢ y₁ = y₂
obtain ⟨p₂₁, p₂₂⟩ := p₂ x:Tmy₁:Tmy₂:Tmp₁₁:x ⟶* y₁p₁₂:IsNormalForm Step y₁p₂₁:x ⟶* y₂p₂₂:IsNormalForm Step y₂⊢ y₁ = y₂
solution!
induction p₁₁ generalizing y₂ with
| refl a => refl x:Tmy₁:Tma:Tmy₂:Tmp₁₂:IsNormalForm Step ap₂₁:a ⟶* y₂p₂₂:IsNormalForm Step y₂⊢ a = y₂
cases p₂₁ with
| refl => refl.refl x:Tmy₁:Tma:Tmp₁₂:IsNormalForm Step ap₂₂:IsNormalForm Step a⊢ a = a rfl All goals completed! 🐙
| step _ b _ h₁ _ => refl.step x:Tmy₁:Tma:Tmy₂:Tmp₁₂:IsNormalForm Step ap₂₂:IsNormalForm Step y₂b:Tmh₁:a ⟶ bh₂✝:b ⟶* y₂⊢ a = y₂ exact absurd ⟨b, h₁⟩ p₁₂ All goals completed! 🐙
| step a b c h₁ h₂ ih => step x:Tmy₁:Tma:Tmb:Tmc:Tmh₁:a ⟶ bh₂:b ⟶* cih:∀ (y₂ : Tm), IsNormalForm Step c → b ⟶* y₂ → IsNormalForm Step y₂ → c = y₂y₂:Tmp₁₂:IsNormalForm Step cp₂₁:a ⟶* y₂p₂₂:IsNormalForm Step y₂⊢ c = y₂
cases p₂₁ with
| refl => step.refl x:Tmy₁:Tma:Tmb:Tmc:Tmh₁:a ⟶ bh₂:b ⟶* cih:∀ (y₂ : Tm), IsNormalForm Step c → b ⟶* y₂ → IsNormalForm Step y₂ → c = y₂p₁₂:IsNormalForm Step cp₂₂:IsNormalForm Step a⊢ c = a exact absurd ⟨b, h₁⟩ p₂₂ All goals completed! 🐙
| step _ b' _ h₁' h₂' => step.step x:Tmy₁:Tma:Tmb:Tmc:Tmh₁:a ⟶ bh₂:b ⟶* cih:∀ (y₂ : Tm), IsNormalForm Step c → b ⟶* y₂ → IsNormalForm Step y₂ → c = y₂y₂:Tmp₁₂:IsNormalForm Step cp₂₂:IsNormalForm Step y₂b':Tmh₁':a ⟶ b'h₂':b' ⟶* y₂⊢ c = y₂
have hbb : b = b' := step_deterministic _ _ _ h₁ h₁' step.step x:Tmy₁:Tma:Tmb:Tmc:Tmh₁:a ⟶ bh₂:b ⟶* cih:∀ (y₂ : Tm), IsNormalForm Step c → b ⟶* y₂ → IsNormalForm Step y₂ → c = y₂y₂:Tmp₁₂:IsNormalForm Step cp₂₂:IsNormalForm Step y₂b':Tmh₁':a ⟶ b'h₂':b' ⟶* y₂hbb:b = b'⊢ c = y₂
subst hbb step.step x:Tmy₁:Tma:Tmb:Tmc:Tmh₁:a ⟶ bh₂:b ⟶* cih:∀ (y₂ : Tm), IsNormalForm Step c → b ⟶* y₂ → IsNormalForm Step y₂ → c = y₂y₂:Tmp₁₂:IsNormalForm Step cp₂₂:IsNormalForm Step y₂h₁':a ⟶ bh₂':b ⟶* y₂⊢ c = y₂
exact ih y₂ p₁₂ h₂' p₂₂ All goals completed! 🐙
Indeed, something stronger is true for this language (though not for all the
languages we will see): the reduction of any term t will eventually
reach a normal form in a finite number of steps — i.e., IsNormalFormOf is
a total function. We say the Step relation is normalizing. To prove
it, we need a couple of congruence lemmas.
def Normalizing {X : Type} (R : Relation X) : Prop :=
∀ t, ∃ t', IsNormalFormOf R t t'
theorem multistep_congr_1 (t₁ t₁' t₂ : Tm) (h : t₁ ⟶* t₁') : (.p t₁ t₂) ⟶* (.p t₁' t₂) := by t₁:Tmt₁':Tmt₂:Tmh:t₁ ⟶* t₁'⊢ t₁.p t₂ ⟶* t₁'.p t₂
induction h with
| refl x => refl t₁:Tmt₁':Tmt₂:Tmx:Tm⊢ x.p t₂ ⟶* x.p t₂ exact .refl _ All goals completed! 🐙
| step x y z h₁ h₂ ih => step t₁:Tmt₁':Tmt₂:Tmx:Tmy:Tmz:Tmh₁:x ⟶ yh₂:y ⟶* zih:y.p t₂ ⟶* z.p t₂⊢ x.p t₂ ⟶* z.p t₂ exact .step _ (.p y t₂) _ (.plusLeft x y t₂ h₁) ih All goals completed! 🐙
theorem multistep_congr_2 (v₁ t₂ t₂' : Tm) (hv : IsValue v₁) (h : t₂ ⟶* t₂') :
(.p v₁ t₂) ⟶* (.p v₁ t₂') := by v₁:Tmt₂:Tmt₂':Tmhv:IsValue v₁h:t₂ ⟶* t₂'⊢ v₁.p t₂ ⟶* v₁.p t₂'
solution!
induction h with
| refl x => refl v₁:Tmt₂:Tmt₂':Tmhv:IsValue v₁x:Tm⊢ v₁.p x ⟶* v₁.p x exact .refl _ All goals completed! 🐙
| step x y z h₁ h₂ ih => step v₁:Tmt₂:Tmt₂':Tmhv:IsValue v₁x:Tmy:Tmz:Tmh₁:x ⟶ yh₂:y ⟶* zih:v₁.p y ⟶* v₁.p z⊢ v₁.p x ⟶* v₁.p z exact .step _ (.p v₁ y) _ (.plusRight v₁ x y hv h₁) ih All goals completed! 🐙
With these lemmas in hand, the main proof is a straightforward induction.
Theorem: The Step relation is normalizing — i.e., for every t there
exists some t' such that t reduces to t' and t' is a normal form.
Proof sketch: By induction on terms. There are two cases:
-
t = c nfor somen. Heretdoesn't take a step, and we havet' = t. We derive the left-hand side by reflexivity and the right-hand side by observing (a) that values are normal forms (bynf_same_as_value) and (b) thattis a value (byconst). -
t = p t₁ t₂for somet₁andt₂. By the IH,t₁andt₂reduce to normal formst₁'andt₂'. Recall that normal forms are values (bynf_same_as_value); we therefore know thatt₁' = c n₁andt₂' = c n₂for somen₁andn₂. We combine the⟶*derivations fort₁andt₂usingmultistep_congr_1andmultistep_congr_2to prove thatp t₁ t₂reduces in many steps tot' = c (n₁ + n₂). Finally,c (n₁ + n₂)is a value, which is in turn a normal form.
theorem step_normalizing : Normalizing Step := by ⊢ Normalizing Step
intro t t:Tm⊢ ∃ t', IsNormalFormOf Step t t'
induction t with
| c n => c n:Nat⊢ ∃ t', IsNormalFormOf Step (Tm.c n) t' exact ⟨.c n, .refl _, (nf_same_as_value _).mpr (.const n)⟩ All goals completed! 🐙
| p t₁ t₂ ih₁ ih₂ => p t₁:Tmt₂:Tmih₁:∃ t', IsNormalFormOf Step t₁ t'ih₂:∃ t', IsNormalFormOf Step t₂ t'⊢ ∃ t', IsNormalFormOf Step (t₁.p t₂) t'
obtain ⟨t₁', hs₁, hnf₁⟩ := ih₁ p t₁:Tmt₂:Tmih₂:∃ t', IsNormalFormOf Step t₂ t't₁':Tmhs₁:t₁ ⟶* t₁'hnf₁:IsNormalForm Step t₁'⊢ ∃ t', IsNormalFormOf Step (t₁.p t₂) t'
obtain ⟨t₂', hs₂, hnf₂⟩ := ih₂ p t₁:Tmt₂:Tmt₁':Tmhs₁:t₁ ⟶* t₁'hnf₁:IsNormalForm Step t₁'t₂':Tmhs₂:t₂ ⟶* t₂'hnf₂:IsNormalForm Step t₂'⊢ ∃ t', IsNormalFormOf Step (t₁.p t₂) t'
obtain ⟨n₁⟩ := (nf_same_as_value _).mp hnf₁ p t₁:Tmt₂:Tmt₂':Tmhs₂:t₂ ⟶* t₂'hnf₂:IsNormalForm Step t₂'n₁:Naths₁:t₁ ⟶* Tm.c n₁hnf₁:IsNormalForm Step (Tm.c n₁)⊢ ∃ t', IsNormalFormOf Step (t₁.p t₂) t'
obtain ⟨n₂⟩ := (nf_same_as_value _).mp hnf₂ p t₁:Tmt₂:Tmn₁:Naths₁:t₁ ⟶* Tm.c n₁hnf₁:IsNormalForm Step (Tm.c n₁)n₂:Naths₂:t₂ ⟶* Tm.c n₂hnf₂:IsNormalForm Step (Tm.c n₂)⊢ ∃ t', IsNormalFormOf Step (t₁.p t₂) t'
apply Exists.intro (.c (n₁ + n₂)) p t₁:Tmt₂:Tmn₁:Naths₁:t₁ ⟶* Tm.c n₁hnf₁:IsNormalForm Step (Tm.c n₁)n₂:Naths₂:t₂ ⟶* Tm.c n₂hnf₂:IsNormalForm Step (Tm.c n₂)⊢ IsNormalFormOf Step (t₁.p t₂) (Tm.c (n₁ + n₂))
apply And.intro _ ((nf_same_as_value _).mpr (.const _)) t₁:Tmt₂:Tmn₁:Naths₁:t₁ ⟶* Tm.c n₁hnf₁:IsNormalForm Step (Tm.c n₁)n₂:Naths₂:t₂ ⟶* Tm.c n₂hnf₂:IsNormalForm Step (Tm.c n₂)⊢ t₁.p t₂ ⟶* Tm.c (n₁ + n₂)
apply multi_trans _ _ _ _ (multistep_congr_1 t₁ (.c n₁) t₂ hs₁) t₁:Tmt₂:Tmn₁:Naths₁:t₁ ⟶* Tm.c n₁hnf₁:IsNormalForm Step (Tm.c n₁)n₂:Naths₂:t₂ ⟶* Tm.c n₂hnf₂:IsNormalForm Step (Tm.c n₂)⊢ (Tm.c n₁).p t₂ ⟶* Tm.c (n₁ + n₂)
apply multi_trans _ _ _ _ (multistep_congr_2 (.c n₁) t₂ (.c n₂) (.const n₁) hs₂) t₁:Tmt₂:Tmn₁:Naths₁:t₁ ⟶* Tm.c n₁hnf₁:IsNormalForm Step (Tm.c n₁)n₂:Naths₂:t₂ ⟶* Tm.c n₂hnf₂:IsNormalForm Step (Tm.c n₂)⊢ (Tm.c n₁).p (Tm.c n₂) ⟶* Tm.c (n₁ + n₂)
exact multi_single _ _ _ (.plus n₁ n₂) All goals completed! 🐙
3.4.3. Equivalence of Big-Step and Small-Step
Having defined the operational semantics of our tiny programming language in two different ways (big-step and small-step), it makes sense to ask whether these definitions actually define the same thing!
They do, though it takes a little work to show it. The details are left as an exercise. We consider the two implications separately. First, big-step evaluation implies multi-step reduction to a value.
theorem multistep_of_eval (t : Tm) (n : Nat) (h : t ⇓ n) : t ⟶* .c n := by t:Tmn:Nath:t ⇓ n⊢ t ⟶* Tm.c n
solution!
induction h with
| const n => const t:Tmn✝:Natn:Nat⊢ Tm.c n ⟶* Tm.c n exact .refl _ All goals completed! 🐙
| plus t₁ t₂ n₁ n₂ h₁ h₂ ih₁ ih₂ => plus t:Tmn:Natt₁:Tmt₂:Tmn₁:Natn₂:Nath₁:t₁ ⇓ n₁h₂:t₂ ⇓ n₂ih₁:t₁ ⟶* Tm.c n₁ih₂:t₂ ⟶* Tm.c n₂⊢ t₁.p t₂ ⟶* Tm.c (n₁ + n₂)
apply multi_trans _ _ _ _ (multistep_congr_1 t₁ (.c n₁) t₂ ih₁) plus t:Tmn:Natt₁:Tmt₂:Tmn₁:Natn₂:Nath₁:t₁ ⇓ n₁h₂:t₂ ⇓ n₂ih₁:t₁ ⟶* Tm.c n₁ih₂:t₂ ⟶* Tm.c n₂⊢ (Tm.c n₁).p t₂ ⟶* Tm.c (n₁ + n₂)
apply multi_trans _ _ _ _ (multistep_congr_2 (.c n₁) t₂ (.c n₂) (.const n₁) ih₂) plus t:Tmn:Natt₁:Tmt₂:Tmn₁:Natn₂:Nath₁:t₁ ⇓ n₁h₂:t₂ ⇓ n₂ih₁:t₁ ⟶* Tm.c n₁ih₂:t₂ ⟶* Tm.c n₂⊢ (Tm.c n₁).p (Tm.c n₂) ⟶* Tm.c (n₁ + n₂)
exact multi_single _ _ _ (.plus n₁ n₂) All goals completed! 🐙
The key ideas in the proof can be seen in the following picture:
p t₁ t₂ ⟶ (by plusLeft) p t₁' t₂ ⟶ (by plusLeft) p t₁'' t₂ ⟶ (by plusLeft) ... p (c n₁) t₂ ⟶ (by plusRight) p (c n₁) t₂' ⟶ (by plusRight) p (c n₁) t₂'' ⟶ (by plusRight) ... p (c n₁) (c n₂) ⟶ (by plus) c (n₁ + n₂)
That is, the multi-step reduction of a term of the form p t₁ t₂ proceeds in
three phases:
-
First, we use
plusLeftsome number of times to reducet₁to a normal form, which must (bynf_same_as_value) be a term of the formc n₁for somen₁. -
Next, we use
plusRightsome number of times to reducet₂to a normal form, which must again be a term of the formc n₂for somen₂. -
Finally, we use
plusone time to reducep (c n₁) (c n₂)toc (n₁ + n₂).
To formalize this intuition, you'll need the congruence lemmas from above,
plus some basic properties of ⟶* (that it is reflexive, transitive, and
includes ⟶).
Write a detailed informal version of the proof of multistep_of_eval. (A
paper exercise — there is no Lean proof to fill in here.)
_Theorem_: for all `t`, `n`, if `t ⇓ n` then `t ⟶* c n`.
_Proof_: By induction on a derivation of `t ⇓ n`.
- Suppose the final rule used to show `t ⇓ n` is `const`. Then `t = c n`.
We must show `c n ⟶* c n`. This holds by `refl`.
- Suppose the final rule used to show `t ⇓ n` is `plus`. Then
`t = p t₁ t₂`, and we know that `t₁ ⇓ c n₁` and `t₂ ⇓ c n₂` for some
`n₁` and `n₂`, with `n = n₁ + n₂`. The IH tells us that `t₁ ⟶* c n₁` and
`t₂ ⟶* c n₂`. We must show that `p t₁ t₂ ⟶* c (n₁ + n₂)`.
First, `p t₁ t₂ ⟶* p (c n₁) t₂` by `multistep_congr_1` and the multistep
derivation for `t₁`. Observing that `c n₁` is a value, we also have
`p (c n₁) t₂ ⟶* p (c n₁) (c n₂)` by `multistep_congr_2` and the multistep
derivation for `t₂`. It's also easy to see by `plus` that
`p (c n₁) (c n₂) ⟶ c (n₁ + n₂)`, and so, by `Step` and
`refl`, that the same is true for `⟶*`. We can now use transitivity
of `⟶*` to stitch these derivations together, proving
`p t₁ t₂ ⟶* c (n₁ + n₂)`.
For the converse, we need one lemma, which establishes a relation between single-step reduction and big-step evaluation. A single step preserves the big-step value.
theorem eval_of_step (t t' : Tm) (n : Nat) (hs : t ⟶ t') (he : t' ⇓ n) : t ⇓ n := by t:Tmt':Tmn:Naths:t ⟶ t'he:t' ⇓ n⊢ t ⇓ n
solution!
induction hs generalizing n with
| plus n₁ n₂ => plus t:Tmt':Tmn₁:Natn₂:Natn:Nathe:Tm.c (n₁ + n₂) ⇓ n⊢ (Tm.c n₁).p (Tm.c n₂) ⇓ n
cases he with
| const _ => plus.const t:Tmt':Tmn₁:Natn₂:Nat⊢ (Tm.c n₁).p (Tm.c n₂) ⇓ n₁ + n₂ exact .plus _ _ n₁ n₂ (.const n₁) (.const n₂) All goals completed! 🐙
| plusLeft t₁ t₁' t₂ h₁ ih => plusLeft t:Tmt':Tmt₁:Tmt₁':Tmt₂:Tmh₁:t₁ ⟶ t₁'ih:∀ (n : Nat), (t₁' ⇓ n) → t₁ ⇓ nn:Nathe:t₁'.p t₂ ⇓ n⊢ t₁.p t₂ ⇓ n
cases he with
| plus _ _ m₁ m₂ he₁ he₂ => plusLeft.plus t:Tmt':Tmt₁:Tmt₁':Tmt₂:Tmh₁:t₁ ⟶ t₁'ih:∀ (n : Nat), (t₁' ⇓ n) → t₁ ⇓ nm₁:Natm₂:Nathe₁:t₁' ⇓ m₁he₂:t₂ ⇓ m₂⊢ t₁.p t₂ ⇓ m₁ + m₂ exact .plus t₁ t₂ m₁ m₂ (ih m₁ he₁) he₂ All goals completed! 🐙
| plusRight v₁ t₂ t₂' hv h₂ ih => plusRight t:Tmt':Tmv₁:Tmt₂:Tmt₂':Tmhv:IsValue v₁h₂:t₂ ⟶ t₂'ih:∀ (n : Nat), (t₂' ⇓ n) → t₂ ⇓ nn:Nathe:v₁.p t₂' ⇓ n⊢ v₁.p t₂ ⇓ n
cases he with
| plus _ _ m₁ m₂ he₁ he₂ => plusRight.plus t:Tmt':Tmv₁:Tmt₂:Tmt₂':Tmhv:IsValue v₁h₂:t₂ ⟶ t₂'ih:∀ (n : Nat), (t₂' ⇓ n) → t₂ ⇓ nm₁:Natm₂:Nathe₁:v₁ ⇓ m₁he₂:t₂' ⇓ m₂⊢ v₁.p t₂ ⇓ m₁ + m₂ exact .plus v₁ t₂ m₁ m₂ he₁ (ih m₂ he₂) All goals completed! 🐙
The fact that small-step reduction implies big-step evaluation is now
straightforward to prove, once we have factored out the observation that
every normal form is a value. The proof proceeds by induction on the
multi-step reduction sequence that is buried in the hypothesis
IsNormalFormOf t t'. (Make sure you understand the statement before you
start to work on the proof.)
theorem eval_of_multistep (t t' : Tm) (h : IsNormalFormOf Step t t') :
∃ n, t' = .c n ∧ t ⇓ n := by t:Tmt':Tmh:IsNormalFormOf Step t t'⊢ ∃ n, t' = Tm.c n ∧ t ⇓ n
solution!
obtain ⟨hs, hnf⟩ := h t:Tmt':Tmhs:t ⟶* t'hnf:IsNormalForm Step t'⊢ ∃ n, t' = Tm.c n ∧ t ⇓ n
obtain ⟨n⟩ := (nf_same_as_value t').mp hnf t:Tmn:Naths:t ⟶* Tm.c nhnf:IsNormalForm Step (Tm.c n)⊢ ∃ n_1, Tm.c n = Tm.c n_1 ∧ t ⇓ n_1
have H : ∀ (a tc : Tm), Multi Step a tc → tc = .c n → a ⇓ n := by t:Tmt':Tmh:IsNormalFormOf Step t t'⊢ ∃ n, t' = Tm.c n ∧ t ⇓ n
intro a tc hst t:Tmn:Naths:t ⟶* Tm.c nhnf:IsNormalForm Step (Tm.c n)a:Tmtc:Tmhst:a ⟶* tc⊢ tc = Tm.c n → a ⇓ n
induction hst with
| refl b => refl t:Tmn:Naths:t ⟶* Tm.c nhnf:IsNormalForm Step (Tm.c n)a:Tmtc:Tmb:Tm⊢ b = Tm.c n → b ⇓ n intro heq refl t:Tmn:Naths:t ⟶* Tm.c nhnf:IsNormalForm Step (Tm.c n)a:Tmtc:Tmb:Tmheq:b = Tm.c n⊢ b ⇓ n; subst heq refl t:Tmn:Naths:t ⟶* Tm.c nhnf:IsNormalForm Step (Tm.c n)a:Tmtc:Tm⊢ Tm.c n ⇓ n; exact .const n All goals completed! 🐙
| step b c d h₁ h₂ ih => step t:Tmn:Naths:t ⟶* Tm.c nhnf:IsNormalForm Step (Tm.c n)a:Tmtc:Tmb:Tmc:Tmd:Tmh₁:b ⟶ ch₂:c ⟶* dih:d = Tm.c n → c ⇓ n⊢ d = Tm.c n → b ⇓ n intro heq step t:Tmn:Naths:t ⟶* Tm.c nhnf:IsNormalForm Step (Tm.c n)a:Tmtc:Tmb:Tmc:Tmd:Tmh₁:b ⟶ ch₂:c ⟶* dih:d = Tm.c n → c ⇓ nheq:d = Tm.c n⊢ b ⇓ n; exact eval_of_step b c n h₁ (ih heq) t:Tmn:Naths:t ⟶* Tm.c nhnf:IsNormalForm Step (Tm.c n)H:∀ (a tc : Tm), a ⟶* tc → tc = Tm.c n → a ⇓ n⊢ ∃ n_1, Tm.c n = Tm.c n_1 ∧ t ⇓ n_1
exact ⟨n, rfl, H t (.c n) hs rfl⟩ All goals completed! 🐙
Remember that we also defined big-step evaluation of terms as a function
evalF. Prove that it is equivalent to the relational semantics. (Hint: we
just proved that Eval and multistep are equivalent, so logically it
doesn't matter which you choose. One will be easier than the other, though!)
theorem evalF_eval (t : Tm) (n : Nat) : evalF t = n ↔ t ⇓ n := by t:Tmn:Nat⊢ evalF t = n ↔ t ⇓ n
solution!
constructor mp t:Tmn:Nat⊢ evalF t = n → t ⇓ nmpr t:Tmn:Nat⊢ (t ⇓ n) → evalF t = n
· mp t:Tmn:Nat⊢ evalF t = n → t ⇓ n intro hi mp t:Tmn:Nathi:evalF t = n⊢ t ⇓ n
subst hi mp t:Tm⊢ t ⇓ evalF t
induction t with
| c n => mp.c n:Nat⊢ Tm.c n ⇓ evalF (Tm.c n) exact .const n All goals completed! 🐙
| p t₁ t₂ ih₁ ih₂ => mp.p t₁:Tmt₂:Tmih₁:t₁ ⇓ evalF t₁ih₂:t₂ ⇓ evalF t₂⊢ t₁.p t₂ ⇓ evalF (t₁.p t₂) exact .plus t₁ t₂ _ _ ih₁ ih₂ All goals completed! 🐙
· mpr t:Tmn:Nat⊢ (t ⇓ n) → evalF t = n intro he mpr t:Tmn:Nathe:t ⇓ n⊢ evalF t = n
induction he with
| const n => mpr.const t:Tmn✝:Natn:Nat⊢ evalF (Tm.c n) = n rfl All goals completed! 🐙
| plus t₁ t₂ n₁ n₂ h₁ h₂ ih₁ ih₂ => mpr.plus t:Tmn:Natt₁:Tmt₂:Tmn₁:Natn₂:Nath₁:t₁ ⇓ n₁h₂:t₂ ⇓ n₂ih₁:evalF t₁ = n₁ih₂:evalF t₂ = n₂⊢ evalF (t₁.p t₂) = n₁ + n₂ simp only [evalF] mpr.plus t:Tmn:Natt₁:Tmt₂:Tmn₁:Natn₂:Nath₁:t₁ ⇓ n₁h₂:t₂ ⇓ n₂ih₁:evalF t₁ = n₁ih₂:evalF t₂ = n₂⊢ evalF t₁ + evalF t₂ = n₁ + n₂; rw [ih₁, mpr.plus t:Tmn:Natt₁:Tmt₂:Tmn₁:Natn₂:Nath₁:t₁ ⇓ n₁h₂:t₂ ⇓ n₂ih₁:evalF t₁ = n₁ih₂:evalF t₂ = n₂⊢ n₁ + evalF t₂ = n₁ + n₂ ih₂ mpr.plus t:Tmn:Natt₁:Tmt₂:Tmn₁:Natn₂:Nath₁:t₁ ⇓ n₁h₂:t₂ ⇓ n₂ih₁:evalF t₁ = n₁ih₂:evalF t₂ = n₂⊢ n₁ + n₂ = n₁ + n₂] All goals completed! 🐙
3.5. Small-Step Slang
Now for a more serious example: a small-step semantics for the richer arithmetic and boolean expressions of the Slang chapter (with subtraction, multiplication, and the boolean operators) rather than the two-constructor toy language we have used so far.
The small-step reduction relations for these expressions are straightforward
extensions of the tiny language we've been working up to now. To make them
easier to read, we introduce the symbolic notations ⟶a and ⟶b for the
arithmetic and boolean step relations.
We work in the Slang namespace, reusing the arithmetic and boolean expression
syntax (Aexp, Bexp) and the big-step evaluator (Aexp.eval) from the
Slang chapter:
namespace Slang
3.5.1. Arithmetic Expressions
The arithmetic values (the normal forms of the small-step relation below) are just the numeric literals:
inductive IsAValue : Aexp → Prop where
| num (n : Nat) : IsAValue (.num n)
Here is the small-step relation for arithmetic expressions. A compound
expression reduces its left operand first; once that is a value, it reduces
its right operand; once both are values, it computes the result. (We show
the rules for + in full; those for − and × have exactly the same
shape.)
a₁ ⟶a a₁'
-------------------- (plusLeft)
a₁ + a₂ ⟶a a₁' + a₂
IsAValue v₁ a₂ ⟶a a₂'
--------------------------- (plusRight)
v₁ + a₂ ⟶a v₁ + a₂'
------------------------- (plus)
n₁ + n₂ ⟶a num (n₁ + n₂)
inductive AStep : Aexp → Aexp → Prop where
| plusLeft (a₁ a₁' a₂ : Aexp) (h : AStep a₁ a₁') : AStep (.plus a₁ a₂) (.plus a₁' a₂)
| plusRight (v₁ a₂ a₂' : Aexp) (hv : IsAValue v₁) (h : AStep a₂ a₂') :
AStep (.plus v₁ a₂) (.plus v₁ a₂')
| plus (n₁ n₂ : Nat) : AStep (.plus (.num n₁) (.num n₂)) (.num (n₁ + n₂))
| minusLeft (a₁ a₁' a₂ : Aexp) (h : AStep a₁ a₁') : AStep (.minus a₁ a₂) (.minus a₁' a₂)
| minusRight (v₁ a₂ a₂' : Aexp) (hv : IsAValue v₁) (h : AStep a₂ a₂') :
AStep (.minus v₁ a₂) (.minus v₁ a₂')
| minus (n₁ n₂ : Nat) : AStep (.minus (.num n₁) (.num n₂)) (.num (n₁ - n₂))
| multLeft (a₁ a₁' a₂ : Aexp) (h : AStep a₁ a₁') : AStep (.mult a₁ a₂) (.mult a₁' a₂)
| multRight (v₁ a₂ a₂' : Aexp) (hv : IsAValue v₁) (h : AStep a₂ a₂') :
AStep (.mult v₁ a₂) (.mult v₁ a₂')
| mult (n₁ n₂ : Nat) : AStep (.mult (.num n₁) (.num n₂)) (.num (n₁ * n₂))
scoped notation:40 a:41 " ⟶a " a':41 => AStep a a'
Notice that AStep has exactly the shape Aexp → Aexp → Prop — i.e., it is a
Relation Aexp in the sense of the Relations section above. So the generic
vocabulary from that section (Deterministic, IsNormalForm, the multi-step
closure Multi, ...) applies to it directly.
Here is a one-step reduction: since the left operand 3 is already a value,
the right operand is the one that takes a step.
example :
(Aexp.plus (.num 3) (.plus (.num 2) (.num 1))) ⟶a (.plus (.num 3) (.num 3)) :=
.plusRight _ _ _ (.num 3) (.plus 2 1)
Every arithmetic expression is either a value or can take a step — the same
strong progress property we proved for the toy language, now for the richer
Slang arithmetic expressions.
theorem strong_progress_arith (a : Aexp) : IsAValue a ∨ ∃ a', a ⟶a a' := by a:Aexp⊢ IsAValue a ∨ ∃ a', a ⟶a a'
solution!
induction a with
| num n => num n:Nat⊢ IsAValue (Aexp.num n) ∨ ∃ a', Aexp.num n ⟶a a' exact .inl (.num n) All goals completed! 🐙
| plus a₁ a₂ ih₁ ih₂ => plus a₁:Aexpa₂:Aexpih₁:IsAValue a₁ ∨ ∃ a', a₁ ⟶a a'ih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'⊢ IsAValue (a₁.plus a₂) ∨ ∃ a', a₁.plus a₂ ⟶a a'
right plus a₁:Aexpa₂:Aexpih₁:IsAValue a₁ ∨ ∃ a', a₁ ⟶a a'ih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'⊢ ∃ a', a₁.plus a₂ ⟶a a'
cases ih₁ with
| inr h₁ => plus.inr a₁:Aexpa₂:Aexpih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'h₁:∃ a', a₁ ⟶a a'⊢ ∃ a', a₁.plus a₂ ⟶a a' obtain ⟨a₁', ha₁⟩ := h₁ plus.inr a₁:Aexpa₂:Aexpih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'a₁':Aexpha₁:a₁ ⟶a a₁'⊢ ∃ a', a₁.plus a₂ ⟶a a'; exact ⟨_, .plusLeft _ _ _ ha₁⟩ All goals completed! 🐙
| inl hv₁ => plus.inl a₁:Aexpa₂:Aexpih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'hv₁:IsAValue a₁⊢ ∃ a', a₁.plus a₂ ⟶a a' cases hv₁ with
| num n₁ => plus.inl.num a₂:Aexpih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'n₁:Nat⊢ ∃ a', (Aexp.num n₁).plus a₂ ⟶a a' cases ih₂ with
| inr h₂ => plus.inl.num.inr a₂:Aexpn₁:Nath₂:∃ a', a₂ ⟶a a'⊢ ∃ a', (Aexp.num n₁).plus a₂ ⟶a a' obtain ⟨a₂', ha₂⟩ := h₂ plus.inl.num.inr a₂:Aexpn₁:Nata₂':Aexpha₂:a₂ ⟶a a₂'⊢ ∃ a', (Aexp.num n₁).plus a₂ ⟶a a'
exact ⟨_, .plusRight _ _ _ (.num n₁) ha₂⟩ All goals completed! 🐙
| inl hv₂ => plus.inl.num.inl a₂:Aexpn₁:Nathv₂:IsAValue a₂⊢ ∃ a', (Aexp.num n₁).plus a₂ ⟶a a' cases hv₂ with
| num n₂ => plus.inl.num.inl.num n₁:Natn₂:Nat⊢ ∃ a', (Aexp.num n₁).plus (Aexp.num n₂) ⟶a a' exact ⟨_, .plus n₁ n₂⟩ All goals completed! 🐙
| minus a₁ a₂ ih₁ ih₂ => minus a₁:Aexpa₂:Aexpih₁:IsAValue a₁ ∨ ∃ a', a₁ ⟶a a'ih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'⊢ IsAValue (a₁.minus a₂) ∨ ∃ a', a₁.minus a₂ ⟶a a'
right minus a₁:Aexpa₂:Aexpih₁:IsAValue a₁ ∨ ∃ a', a₁ ⟶a a'ih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'⊢ ∃ a', a₁.minus a₂ ⟶a a'
cases ih₁ with
| inr h₁ => minus.inr a₁:Aexpa₂:Aexpih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'h₁:∃ a', a₁ ⟶a a'⊢ ∃ a', a₁.minus a₂ ⟶a a' obtain ⟨a₁', ha₁⟩ := h₁ minus.inr a₁:Aexpa₂:Aexpih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'a₁':Aexpha₁:a₁ ⟶a a₁'⊢ ∃ a', a₁.minus a₂ ⟶a a'; exact ⟨_, .minusLeft _ _ _ ha₁⟩ All goals completed! 🐙
| inl hv₁ => minus.inl a₁:Aexpa₂:Aexpih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'hv₁:IsAValue a₁⊢ ∃ a', a₁.minus a₂ ⟶a a' cases hv₁ with
| num n₁ => minus.inl.num a₂:Aexpih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'n₁:Nat⊢ ∃ a', (Aexp.num n₁).minus a₂ ⟶a a' cases ih₂ with
| inr h₂ => minus.inl.num.inr a₂:Aexpn₁:Nath₂:∃ a', a₂ ⟶a a'⊢ ∃ a', (Aexp.num n₁).minus a₂ ⟶a a' obtain ⟨a₂', ha₂⟩ := h₂ minus.inl.num.inr a₂:Aexpn₁:Nata₂':Aexpha₂:a₂ ⟶a a₂'⊢ ∃ a', (Aexp.num n₁).minus a₂ ⟶a a'
exact ⟨_, .minusRight _ _ _ (.num n₁) ha₂⟩ All goals completed! 🐙
| inl hv₂ => minus.inl.num.inl a₂:Aexpn₁:Nathv₂:IsAValue a₂⊢ ∃ a', (Aexp.num n₁).minus a₂ ⟶a a' cases hv₂ with
| num n₂ => minus.inl.num.inl.num n₁:Natn₂:Nat⊢ ∃ a', (Aexp.num n₁).minus (Aexp.num n₂) ⟶a a' exact ⟨_, .minus n₁ n₂⟩ All goals completed! 🐙
| mult a₁ a₂ ih₁ ih₂ => mult a₁:Aexpa₂:Aexpih₁:IsAValue a₁ ∨ ∃ a', a₁ ⟶a a'ih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'⊢ IsAValue (a₁.mult a₂) ∨ ∃ a', a₁.mult a₂ ⟶a a'
right mult a₁:Aexpa₂:Aexpih₁:IsAValue a₁ ∨ ∃ a', a₁ ⟶a a'ih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'⊢ ∃ a', a₁.mult a₂ ⟶a a'
cases ih₁ with
| inr h₁ => mult.inr a₁:Aexpa₂:Aexpih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'h₁:∃ a', a₁ ⟶a a'⊢ ∃ a', a₁.mult a₂ ⟶a a' obtain ⟨a₁', ha₁⟩ := h₁ mult.inr a₁:Aexpa₂:Aexpih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'a₁':Aexpha₁:a₁ ⟶a a₁'⊢ ∃ a', a₁.mult a₂ ⟶a a'; exact ⟨_, .multLeft _ _ _ ha₁⟩ All goals completed! 🐙
| inl hv₁ => mult.inl a₁:Aexpa₂:Aexpih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'hv₁:IsAValue a₁⊢ ∃ a', a₁.mult a₂ ⟶a a' cases hv₁ with
| num n₁ => mult.inl.num a₂:Aexpih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'n₁:Nat⊢ ∃ a', (Aexp.num n₁).mult a₂ ⟶a a' cases ih₂ with
| inr h₂ => mult.inl.num.inr a₂:Aexpn₁:Nath₂:∃ a', a₂ ⟶a a'⊢ ∃ a', (Aexp.num n₁).mult a₂ ⟶a a' obtain ⟨a₂', ha₂⟩ := h₂ mult.inl.num.inr a₂:Aexpn₁:Nata₂':Aexpha₂:a₂ ⟶a a₂'⊢ ∃ a', (Aexp.num n₁).mult a₂ ⟶a a'
exact ⟨_, .multRight _ _ _ (.num n₁) ha₂⟩ All goals completed! 🐙
| inl hv₂ => mult.inl.num.inl a₂:Aexpn₁:Nathv₂:IsAValue a₂⊢ ∃ a', (Aexp.num n₁).mult a₂ ⟶a a' cases hv₂ with
| num n₂ => mult.inl.num.inl.num n₁:Natn₂:Nat⊢ ∃ a', (Aexp.num n₁).mult (Aexp.num n₂) ⟶a a' exact ⟨_, .mult n₁ n₂⟩ All goals completed! 🐙
3.5.2. Boolean Expressions
The small-step relation for boolean expressions reduces the arithmetic
subexpressions of a comparison (using ⟶a) and then applies the comparison,
and it short-circuits ¬ and ∧ on boolean literals.
We are not actually going to bother to define boolean values, since they
aren't needed in the definition of ⟶b below (why?), though they might be if
our language were a bit more complicated (why?).
Again we show a
representative sample; neq, le, and gt follow the same pattern as eq.
a₁ ⟶a a₁'
-------------------- (eqLeft)
a₁ = a₂ ⟶b a₁' = a₂
IsAValue v₁ a₂ ⟶a a₂'
--------------------------- (eqRight)
v₁ = a₂ ⟶b v₁ = a₂'
--------------------- (eq)
n₁ = n₂ ⟶b (n₁ = n₂)
b₁ ⟶b b₁'
-------------- (notStep)
¬ b₁ ⟶b ¬ b₁'
---------------- (notTrue)
¬ true ⟶b false
--------------------- (andFalse)
false ∧ b₂ ⟶b false
Here are the formal rules.
inductive BStep : Bexp → Bexp → Prop where
| eqLeft (a₁ a₁' a₂ : Aexp) (h : AStep a₁ a₁') : BStep (.eq a₁ a₂) (.eq a₁' a₂)
| eqRight (v₁ a₂ a₂' : Aexp) (hv : IsAValue v₁) (h : AStep a₂ a₂') :
BStep (.eq v₁ a₂) (.eq v₁ a₂')
| eq (n₁ n₂ : Nat) : BStep (.eq (.num n₁) (.num n₂)) (.bool (decide (n₁ = n₂)))
| neqLeft (a₁ a₁' a₂ : Aexp) (h : AStep a₁ a₁') : BStep (.neq a₁ a₂) (.neq a₁' a₂)
| neqRight (v₁ a₂ a₂' : Aexp) (hv : IsAValue v₁) (h : AStep a₂ a₂') :
BStep (.neq v₁ a₂) (.neq v₁ a₂')
| neq (n₁ n₂ : Nat) : BStep (.neq (.num n₁) (.num n₂)) (.bool (decide (n₁ ≠ n₂)))
| leLeft (a₁ a₁' a₂ : Aexp) (h : AStep a₁ a₁') : BStep (.le a₁ a₂) (.le a₁' a₂)
| leRight (v₁ a₂ a₂' : Aexp) (hv : IsAValue v₁) (h : AStep a₂ a₂') :
BStep (.le v₁ a₂) (.le v₁ a₂')
| le (n₁ n₂ : Nat) : BStep (.le (.num n₁) (.num n₂)) (.bool (decide (n₁ ≤ n₂)))
| gtLeft (a₁ a₁' a₂ : Aexp) (h : AStep a₁ a₁') : BStep (.gt a₁ a₂) (.gt a₁' a₂)
| gtRight (v₁ a₂ a₂' : Aexp) (hv : IsAValue v₁) (h : AStep a₂ a₂') :
BStep (.gt v₁ a₂) (.gt v₁ a₂')
| gt (n₁ n₂ : Nat) : BStep (.gt (.num n₁) (.num n₂)) (.bool (decide (n₁ > n₂)))
| notStep (b₁ b₁' : Bexp) (h : BStep b₁ b₁') : BStep (.not b₁) (.not b₁')
| notTrue : BStep (.not (.bool true)) (.bool false)
| notFalse : BStep (.not (.bool false)) (.bool true)
| andStep (b₁ b₁' b₂ : Bexp) (h : BStep b₁ b₁') : BStep (.and b₁ b₂) (.and b₁' b₂)
| andTrueStep (b₂ b₂' : Bexp) (h : BStep b₂ b₂') :
BStep (.and (.bool true) b₂) (.and (.bool true) b₂')
| andFalse (b₂ : Bexp) : BStep (.and (.bool false) b₂) (.bool false)
| andTrueTrue : BStep (.and (.bool true) (.bool true)) (.bool true)
| andTrueFalse : BStep (.and (.bool true) (.bool false)) (.bool false)
scoped notation:40 b:41 " ⟶b " b':41 => BStep b b'
A boolean example — the left comparison operand reduces first:
example :
(Bexp.le (.plus (.num 1) (.num 1)) (.num 3)) ⟶b (.le (.num 2) (.num 3)) :=
.leLeft _ _ _ (.plus 1 1)
Which of these properties does this small-step semantics for Slang
expressions satisfy? (Yes or No for each.)
-
determinism
-
strong progress (every non-value takes a step)
-
values and normal forms coincide (i.e., there are no "stuck" terms)
-
the step relation is normalizing (i.e., evaluation always terminates)
Show solution
Yes to all four. Expression evaluation always terminates, so ⟶a (and ⟶b) are normalizing.
Let us make good on the first of those answers. Both step relations are deterministic: the value guards on the "step the right operand" rules mean that at most one rule ever applies to a given term.
The arithmetic step relation is deterministic. (Structurally this is the
value-based determinism proof from the toy language, repeated for +, −,
and ×; the impossible cross-cases close because a value num n cannot step.)
theorem astep_deterministic : Deterministic AStep := by ⊢ Deterministic AStep
solution!
intro x y₁ y₂ h₁ x:Aexpy₁:Aexpy₂:Aexph₁:x ⟶a y₁⊢ x ⟶a y₂ → y₁ = y₂
induction h₁ generalizing y₂ plusLeft x:Aexpy₁:Aexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝h_ih✝:∀ (y₂ : Aexp), a₁✝ ⟶a y₂ → a₁'✝ = y₂y₂:Aexp⊢ a₁✝.plus a₂✝ ⟶a y₂ → a₁'✝.plus a₂✝ = y₂plusRight x:Aexpy₁:Aexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝h_ih✝:∀ (y₂ : Aexp), a₂✝ ⟶a y₂ → a₂'✝ = y₂y₂:Aexp⊢ v₁✝.plus a₂✝ ⟶a y₂ → v₁✝.plus a₂'✝ = y₂plus x:Aexpy₁:Aexpn₁✝:Natn₂✝:Naty₂:Aexp⊢ (Aexp.num n₁✝).plus (Aexp.num n₂✝) ⟶a y₂ → Aexp.num (n₁✝ + n₂✝) = y₂minusLeft x:Aexpy₁:Aexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝h_ih✝:∀ (y₂ : Aexp), a₁✝ ⟶a y₂ → a₁'✝ = y₂y₂:Aexp⊢ a₁✝.minus a₂✝ ⟶a y₂ → a₁'✝.minus a₂✝ = y₂minusRight x:Aexpy₁:Aexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝h_ih✝:∀ (y₂ : Aexp), a₂✝ ⟶a y₂ → a₂'✝ = y₂y₂:Aexp⊢ v₁✝.minus a₂✝ ⟶a y₂ → v₁✝.minus a₂'✝ = y₂minus x:Aexpy₁:Aexpn₁✝:Natn₂✝:Naty₂:Aexp⊢ (Aexp.num n₁✝).minus (Aexp.num n₂✝) ⟶a y₂ → Aexp.num (n₁✝ - n₂✝) = y₂multLeft x:Aexpy₁:Aexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝h_ih✝:∀ (y₂ : Aexp), a₁✝ ⟶a y₂ → a₁'✝ = y₂y₂:Aexp⊢ a₁✝.mult a₂✝ ⟶a y₂ → a₁'✝.mult a₂✝ = y₂multRight x:Aexpy₁:Aexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝h_ih✝:∀ (y₂ : Aexp), a₂✝ ⟶a y₂ → a₂'✝ = y₂y₂:Aexp⊢ v₁✝.mult a₂✝ ⟶a y₂ → v₁✝.mult a₂'✝ = y₂mult x:Aexpy₁:Aexpn₁✝:Natn₂✝:Naty₂:Aexp⊢ (Aexp.num n₁✝).mult (Aexp.num n₂✝) ⟶a y₂ → Aexp.num (n₁✝ * n₂✝) = y₂ <;> plusLeft x:Aexpy₁:Aexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝h_ih✝:∀ (y₂ : Aexp), a₁✝ ⟶a y₂ → a₁'✝ = y₂y₂:Aexp⊢ a₁✝.plus a₂✝ ⟶a y₂ → a₁'✝.plus a₂✝ = y₂plusRight x:Aexpy₁:Aexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝h_ih✝:∀ (y₂ : Aexp), a₂✝ ⟶a y₂ → a₂'✝ = y₂y₂:Aexp⊢ v₁✝.plus a₂✝ ⟶a y₂ → v₁✝.plus a₂'✝ = y₂plus x:Aexpy₁:Aexpn₁✝:Natn₂✝:Naty₂:Aexp⊢ (Aexp.num n₁✝).plus (Aexp.num n₂✝) ⟶a y₂ → Aexp.num (n₁✝ + n₂✝) = y₂minusLeft x:Aexpy₁:Aexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝h_ih✝:∀ (y₂ : Aexp), a₁✝ ⟶a y₂ → a₁'✝ = y₂y₂:Aexp⊢ a₁✝.minus a₂✝ ⟶a y₂ → a₁'✝.minus a₂✝ = y₂minusRight x:Aexpy₁:Aexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝h_ih✝:∀ (y₂ : Aexp), a₂✝ ⟶a y₂ → a₂'✝ = y₂y₂:Aexp⊢ v₁✝.minus a₂✝ ⟶a y₂ → v₁✝.minus a₂'✝ = y₂minus x:Aexpy₁:Aexpn₁✝:Natn₂✝:Naty₂:Aexp⊢ (Aexp.num n₁✝).minus (Aexp.num n₂✝) ⟶a y₂ → Aexp.num (n₁✝ - n₂✝) = y₂multLeft x:Aexpy₁:Aexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝h_ih✝:∀ (y₂ : Aexp), a₁✝ ⟶a y₂ → a₁'✝ = y₂y₂:Aexp⊢ a₁✝.mult a₂✝ ⟶a y₂ → a₁'✝.mult a₂✝ = y₂multRight x:Aexpy₁:Aexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝h_ih✝:∀ (y₂ : Aexp), a₂✝ ⟶a y₂ → a₂'✝ = y₂y₂:Aexp⊢ v₁✝.mult a₂✝ ⟶a y₂ → v₁✝.mult a₂'✝ = y₂mult x:Aexpy₁:Aexpn₁✝:Natn₂✝:Naty₂:Aexp⊢ (Aexp.num n₁✝).mult (Aexp.num n₂✝) ⟶a y₂ → Aexp.num (n₁✝ * n₂✝) = y₂ intro h₂ mult x:Aexpy₁:Aexpn₁✝:Natn₂✝:Naty₂:Aexph₂:(Aexp.num n₁✝).mult (Aexp.num n₂✝) ⟶a y₂⊢ Aexp.num (n₁✝ * n₂✝) = y₂ <;> plusLeft x:Aexpy₁:Aexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝h_ih✝:∀ (y₂ : Aexp), a₁✝ ⟶a y₂ → a₁'✝ = y₂y₂:Aexph₂:a₁✝.plus a₂✝ ⟶a y₂⊢ a₁'✝.plus a₂✝ = y₂plusRight x:Aexpy₁:Aexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝h_ih✝:∀ (y₂ : Aexp), a₂✝ ⟶a y₂ → a₂'✝ = y₂y₂:Aexph₂:v₁✝.plus a₂✝ ⟶a y₂⊢ v₁✝.plus a₂'✝ = y₂plus x:Aexpy₁:Aexpn₁✝:Natn₂✝:Naty₂:Aexph₂:(Aexp.num n₁✝).plus (Aexp.num n₂✝) ⟶a y₂⊢ Aexp.num (n₁✝ + n₂✝) = y₂minusLeft x:Aexpy₁:Aexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝h_ih✝:∀ (y₂ : Aexp), a₁✝ ⟶a y₂ → a₁'✝ = y₂y₂:Aexph₂:a₁✝.minus a₂✝ ⟶a y₂⊢ a₁'✝.minus a₂✝ = y₂minusRight x:Aexpy₁:Aexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝h_ih✝:∀ (y₂ : Aexp), a₂✝ ⟶a y₂ → a₂'✝ = y₂y₂:Aexph₂:v₁✝.minus a₂✝ ⟶a y₂⊢ v₁✝.minus a₂'✝ = y₂minus x:Aexpy₁:Aexpn₁✝:Natn₂✝:Naty₂:Aexph₂:(Aexp.num n₁✝).minus (Aexp.num n₂✝) ⟶a y₂⊢ Aexp.num (n₁✝ - n₂✝) = y₂multLeft x:Aexpy₁:Aexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝h_ih✝:∀ (y₂ : Aexp), a₁✝ ⟶a y₂ → a₁'✝ = y₂y₂:Aexph₂:a₁✝.mult a₂✝ ⟶a y₂⊢ a₁'✝.mult a₂✝ = y₂multRight x:Aexpy₁:Aexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝h_ih✝:∀ (y₂ : Aexp), a₂✝ ⟶a y₂ → a₂'✝ = y₂y₂:Aexph₂:v₁✝.mult a₂✝ ⟶a y₂⊢ v₁✝.mult a₂'✝ = y₂mult x:Aexpy₁:Aexpn₁✝:Natn₂✝:Naty₂:Aexph₂:(Aexp.num n₁✝).mult (Aexp.num n₂✝) ⟶a y₂⊢ Aexp.num (n₁✝ * n₂✝) = y₂ cases h₂ mult.multLeft x:Aexpy₁:Aexpn₁✝:Natn₂✝:Nata₁'✝:Aexph✝:Aexp.num n₁✝ ⟶a a₁'✝⊢ Aexp.num (n₁✝ * n₂✝) = a₁'✝.mult (Aexp.num n₂✝)mult.multRight x:Aexpy₁:Aexpn₁✝:Natn₂✝:Nata₂'✝:Aexphv✝:IsAValue (Aexp.num n₁✝)h✝:Aexp.num n₂✝ ⟶a a₂'✝⊢ Aexp.num (n₁✝ * n₂✝) = (Aexp.num n₁✝).mult a₂'✝mult.mult x:Aexpy₁:Aexpn₁✝:Natn₂✝:Nat⊢ Aexp.num (n₁✝ * n₂✝) = Aexp.num (n₁✝ * n₂✝) <;> plusLeft.plusLeft x:Aexpy₁:Aexpa₁✝:Aexpa₁'✝¹:Aexpa₂✝:Aexph✝¹:a₁✝ ⟶a a₁'✝h_ih✝:∀ (y₂ : Aexp), a₁✝ ⟶a y₂ → a₁'✝ = y₂a₁'✝:Aexph✝:a₁✝ ⟶a a₁'✝⊢ a₁'✝¹.plus a₂✝ = a₁'✝.plus a₂✝plusLeft.plusRight x:Aexpy₁:Aexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝¹:a₁✝ ⟶a a₁'✝h_ih✝:∀ (y₂ : Aexp), a₁✝ ⟶a y₂ → a₁'✝ = y₂a₂'✝:Aexphv✝:IsAValue a₁✝h✝:a₂✝ ⟶a a₂'✝⊢ a₁'✝.plus a₂✝ = a₁✝.plus a₂'✝plusLeft.plus x:Aexpy₁:Aexpa₁'✝:Aexpn₁✝:Natn₂✝:Nath✝:Aexp.num n₁✝ ⟶a a₁'✝h_ih✝:∀ (y₂ : Aexp), Aexp.num n₁✝ ⟶a y₂ → a₁'✝ = y₂⊢ a₁'✝.plus (Aexp.num n₂✝) = Aexp.num (n₁✝ + n₂✝)plusRight.plusLeft x:Aexpy₁:Aexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝¹:a₂✝ ⟶a a₂'✝h_ih✝:∀ (y₂ : Aexp), a₂✝ ⟶a y₂ → a₂'✝ = y₂a₁'✝:Aexph✝:v₁✝ ⟶a a₁'✝⊢ v₁✝.plus a₂'✝ = a₁'✝.plus a₂✝plusRight.plusRight x:Aexpy₁:Aexpv₁✝:Aexpa₂✝:Aexpa₂'✝¹:Aexphv✝¹:IsAValue v₁✝h✝¹:a₂✝ ⟶a a₂'✝h_ih✝:∀ (y₂ : Aexp), a₂✝ ⟶a y₂ → a₂'✝ = y₂a₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝⊢ v₁✝.plus a₂'✝¹ = v₁✝.plus a₂'✝plusRight.plus x:Aexpy₁:Aexpa₂'✝:Aexpn₁✝:Natn₂✝:Nathv✝:IsAValue (Aexp.num n₁✝)h✝:Aexp.num n₂✝ ⟶a a₂'✝h_ih✝:∀ (y₂ : Aexp), Aexp.num n₂✝ ⟶a y₂ → a₂'✝ = y₂⊢ (Aexp.num n₁✝).plus a₂'✝ = Aexp.num (n₁✝ + n₂✝)plus.plusLeft x:Aexpy₁:Aexpn₁✝:Natn₂✝:Nata₁'✝:Aexph✝:Aexp.num n₁✝ ⟶a a₁'✝⊢ Aexp.num (n₁✝ + n₂✝) = a₁'✝.plus (Aexp.num n₂✝)plus.plusRight x:Aexpy₁:Aexpn₁✝:Natn₂✝:Nata₂'✝:Aexphv✝:IsAValue (Aexp.num n₁✝)h✝:Aexp.num n₂✝ ⟶a a₂'✝⊢ Aexp.num (n₁✝ + n₂✝) = (Aexp.num n₁✝).plus a₂'✝plus.plus x:Aexpy₁:Aexpn₁✝:Natn₂✝:Nat⊢ Aexp.num (n₁✝ + n₂✝) = Aexp.num (n₁✝ + n₂✝)minusLeft.minusLeft x:Aexpy₁:Aexpa₁✝:Aexpa₁'✝¹:Aexpa₂✝:Aexph✝¹:a₁✝ ⟶a a₁'✝h_ih✝:∀ (y₂ : Aexp), a₁✝ ⟶a y₂ → a₁'✝ = y₂a₁'✝:Aexph✝:a₁✝ ⟶a a₁'✝⊢ a₁'✝¹.minus a₂✝ = a₁'✝.minus a₂✝minusLeft.minusRight x:Aexpy₁:Aexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝¹:a₁✝ ⟶a a₁'✝h_ih✝:∀ (y₂ : Aexp), a₁✝ ⟶a y₂ → a₁'✝ = y₂a₂'✝:Aexphv✝:IsAValue a₁✝h✝:a₂✝ ⟶a a₂'✝⊢ a₁'✝.minus a₂✝ = a₁✝.minus a₂'✝minusLeft.minus x:Aexpy₁:Aexpa₁'✝:Aexpn₁✝:Natn₂✝:Nath✝:Aexp.num n₁✝ ⟶a a₁'✝h_ih✝:∀ (y₂ : Aexp), Aexp.num n₁✝ ⟶a y₂ → a₁'✝ = y₂⊢ a₁'✝.minus (Aexp.num n₂✝) = Aexp.num (n₁✝ - n₂✝)minusRight.minusLeft x:Aexpy₁:Aexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝¹:a₂✝ ⟶a a₂'✝h_ih✝:∀ (y₂ : Aexp), a₂✝ ⟶a y₂ → a₂'✝ = y₂a₁'✝:Aexph✝:v₁✝ ⟶a a₁'✝⊢ v₁✝.minus a₂'✝ = a₁'✝.minus a₂✝minusRight.minusRight x:Aexpy₁:Aexpv₁✝:Aexpa₂✝:Aexpa₂'✝¹:Aexphv✝¹:IsAValue v₁✝h✝¹:a₂✝ ⟶a a₂'✝h_ih✝:∀ (y₂ : Aexp), a₂✝ ⟶a y₂ → a₂'✝ = y₂a₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝⊢ v₁✝.minus a₂'✝¹ = v₁✝.minus a₂'✝minusRight.minus x:Aexpy₁:Aexpa₂'✝:Aexpn₁✝:Natn₂✝:Nathv✝:IsAValue (Aexp.num n₁✝)h✝:Aexp.num n₂✝ ⟶a a₂'✝h_ih✝:∀ (y₂ : Aexp), Aexp.num n₂✝ ⟶a y₂ → a₂'✝ = y₂⊢ (Aexp.num n₁✝).minus a₂'✝ = Aexp.num (n₁✝ - n₂✝)minus.minusLeft x:Aexpy₁:Aexpn₁✝:Natn₂✝:Nata₁'✝:Aexph✝:Aexp.num n₁✝ ⟶a a₁'✝⊢ Aexp.num (n₁✝ - n₂✝) = a₁'✝.minus (Aexp.num n₂✝)minus.minusRight x:Aexpy₁:Aexpn₁✝:Natn₂✝:Nata₂'✝:Aexphv✝:IsAValue (Aexp.num n₁✝)h✝:Aexp.num n₂✝ ⟶a a₂'✝⊢ Aexp.num (n₁✝ - n₂✝) = (Aexp.num n₁✝).minus a₂'✝minus.minus x:Aexpy₁:Aexpn₁✝:Natn₂✝:Nat⊢ Aexp.num (n₁✝ - n₂✝) = Aexp.num (n₁✝ - n₂✝)multLeft.multLeft x:Aexpy₁:Aexpa₁✝:Aexpa₁'✝¹:Aexpa₂✝:Aexph✝¹:a₁✝ ⟶a a₁'✝h_ih✝:∀ (y₂ : Aexp), a₁✝ ⟶a y₂ → a₁'✝ = y₂a₁'✝:Aexph✝:a₁✝ ⟶a a₁'✝⊢ a₁'✝¹.mult a₂✝ = a₁'✝.mult a₂✝multLeft.multRight x:Aexpy₁:Aexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝¹:a₁✝ ⟶a a₁'✝h_ih✝:∀ (y₂ : Aexp), a₁✝ ⟶a y₂ → a₁'✝ = y₂a₂'✝:Aexphv✝:IsAValue a₁✝h✝:a₂✝ ⟶a a₂'✝⊢ a₁'✝.mult a₂✝ = a₁✝.mult a₂'✝multLeft.mult x:Aexpy₁:Aexpa₁'✝:Aexpn₁✝:Natn₂✝:Nath✝:Aexp.num n₁✝ ⟶a a₁'✝h_ih✝:∀ (y₂ : Aexp), Aexp.num n₁✝ ⟶a y₂ → a₁'✝ = y₂⊢ a₁'✝.mult (Aexp.num n₂✝) = Aexp.num (n₁✝ * n₂✝)multRight.multLeft x:Aexpy₁:Aexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝¹:a₂✝ ⟶a a₂'✝h_ih✝:∀ (y₂ : Aexp), a₂✝ ⟶a y₂ → a₂'✝ = y₂a₁'✝:Aexph✝:v₁✝ ⟶a a₁'✝⊢ v₁✝.mult a₂'✝ = a₁'✝.mult a₂✝multRight.multRight x:Aexpy₁:Aexpv₁✝:Aexpa₂✝:Aexpa₂'✝¹:Aexphv✝¹:IsAValue v₁✝h✝¹:a₂✝ ⟶a a₂'✝h_ih✝:∀ (y₂ : Aexp), a₂✝ ⟶a y₂ → a₂'✝ = y₂a₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝⊢ v₁✝.mult a₂'✝¹ = v₁✝.mult a₂'✝multRight.mult x:Aexpy₁:Aexpa₂'✝:Aexpn₁✝:Natn₂✝:Nathv✝:IsAValue (Aexp.num n₁✝)h✝:Aexp.num n₂✝ ⟶a a₂'✝h_ih✝:∀ (y₂ : Aexp), Aexp.num n₂✝ ⟶a y₂ → a₂'✝ = y₂⊢ (Aexp.num n₁✝).mult a₂'✝ = Aexp.num (n₁✝ * n₂✝)mult.multLeft x:Aexpy₁:Aexpn₁✝:Natn₂✝:Nata₁'✝:Aexph✝:Aexp.num n₁✝ ⟶a a₁'✝⊢ Aexp.num (n₁✝ * n₂✝) = a₁'✝.mult (Aexp.num n₂✝)mult.multRight x:Aexpy₁:Aexpn₁✝:Natn₂✝:Nata₂'✝:Aexphv✝:IsAValue (Aexp.num n₁✝)h✝:Aexp.num n₂✝ ⟶a a₂'✝⊢ Aexp.num (n₁✝ * n₂✝) = (Aexp.num n₁✝).mult a₂'✝mult.mult x:Aexpy₁:Aexpn₁✝:Natn₂✝:Nat⊢ Aexp.num (n₁✝ * n₂✝) = Aexp.num (n₁✝ * n₂✝)
first
| rfl All goals completed! 🐙
| cases ‹AStep (Aexp.num _) _› All goals completed! 🐙
| (cases ‹IsAValue _› multRight.multRight.num x:Aexpy₁:Aexpa₂✝:Aexpa₂'✝¹:Aexph✝¹:a₂✝ ⟶a a₂'✝h_ih✝:∀ (y₂ : Aexp), a₂✝ ⟶a y₂ → a₂'✝ = y₂a₂'✝:Aexph✝:a₂✝ ⟶a a₂'✝n✝:Nathv✝:IsAValue (Aexp.num n✝)⊢ (Aexp.num n✝).mult a₂'✝¹ = (Aexp.num n✝).mult a₂'✝; cases ‹AStep (Aexp.num _) _› multRight.multRight.num x:Aexpy₁:Aexpa₂✝:Aexpa₂'✝¹:Aexph✝¹:a₂✝ ⟶a a₂'✝h_ih✝:∀ (y₂ : Aexp), a₂✝ ⟶a y₂ → a₂'✝ = y₂a₂'✝:Aexph✝:a₂✝ ⟶a a₂'✝n✝:Nathv✝:IsAValue (Aexp.num n✝)⊢ (Aexp.num n✝).mult a₂'✝¹ = (Aexp.num n✝).mult a₂'✝)
| (congr 1 multRight.multRight.e_a₂ x:Aexpy₁:Aexpv₁✝:Aexpa₂✝:Aexpa₂'✝¹:Aexphv✝¹:IsAValue v₁✝h✝¹:a₂✝ ⟶a a₂'✝h_ih✝:∀ (y₂ : Aexp), a₂✝ ⟶a y₂ → a₂'✝ = y₂a₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝⊢ a₂'✝¹ = a₂'✝ <;> multRight.multRight.e_a₂ x:Aexpy₁:Aexpv₁✝:Aexpa₂✝:Aexpa₂'✝¹:Aexphv✝¹:IsAValue v₁✝h✝¹:a₂✝ ⟶a a₂'✝h_ih✝:∀ (y₂ : Aexp), a₂✝ ⟶a y₂ → a₂'✝ = y₂a₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝⊢ a₂'✝¹ = a₂'✝ first | rfl multRight.multRight.e_a₂ x:Aexpy₁:Aexpv₁✝:Aexpa₂✝:Aexpa₂'✝¹:Aexphv✝¹:IsAValue v₁✝h✝¹:a₂✝ ⟶a a₂'✝h_ih✝:∀ (y₂ : Aexp), a₂✝ ⟶a y₂ → a₂'✝ = y₂a₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝⊢ a₂'✝¹ = a₂'✝ | (apply ‹∀ _, AStep _ _ → _ = _› multRight.multRight.e_a₂ x:Aexpy₁:Aexpv₁✝:Aexpa₂✝:Aexpa₂'✝¹:Aexphv✝¹:IsAValue v₁✝h✝¹:a₂✝ ⟶a a₂'✝h_ih✝:∀ (y₂ : Aexp), a₂✝ ⟶a y₂ → a₂'✝ = y₂a₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝⊢ a₂✝ ⟶a a₂'✝ <;> multRight.multRight.e_a₂ x:Aexpy₁:Aexpv₁✝:Aexpa₂✝:Aexpa₂'✝¹:Aexphv✝¹:IsAValue v₁✝h✝¹:a₂✝ ⟶a a₂'✝h_ih✝:∀ (y₂ : Aexp), a₂✝ ⟶a y₂ → a₂'✝ = y₂a₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝⊢ a₂✝ ⟶a a₂'✝ assumption All goals completed! 🐙))
The boolean step relation is deterministic too. The comparison cases (eq,
neq, le, gt) reduce their operands with ⟶a, so they inherit determinism
from astep_deterministic; ¬ and the short-circuiting ∧ contribute only
base cases.
theorem bstep_deterministic : Deterministic BStep := by ⊢ Deterministic BStep
solution!
intro x y₁ y₂ h₁ x:Bexpy₁:Bexpy₂:Bexph₁:x ⟶b y₁⊢ x ⟶b y₂ → y₁ = y₂
induction h₁ generalizing y₂ eqLeft x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝y₂:Bexp⊢ Bexp.eq a₁✝ a₂✝ ⟶b y₂ → Bexp.eq a₁'✝ a₂✝ = y₂eqRight x:Bexpy₁:Bexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝y₂:Bexp⊢ Bexp.eq v₁✝ a₂✝ ⟶b y₂ → Bexp.eq v₁✝ a₂'✝ = y₂eq x:Bexpy₁:Bexpn₁✝:Natn₂✝:Naty₂:Bexp⊢ Bexp.eq (Aexp.num n₁✝) (Aexp.num n₂✝) ⟶b y₂ → Bexp.bool (decide (n₁✝ = n₂✝)) = y₂neqLeft x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝y₂:Bexp⊢ Bexp.neq a₁✝ a₂✝ ⟶b y₂ → Bexp.neq a₁'✝ a₂✝ = y₂neqRight x:Bexpy₁:Bexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝y₂:Bexp⊢ Bexp.neq v₁✝ a₂✝ ⟶b y₂ → Bexp.neq v₁✝ a₂'✝ = y₂neq x:Bexpy₁:Bexpn₁✝:Natn₂✝:Naty₂:Bexp⊢ Bexp.neq (Aexp.num n₁✝) (Aexp.num n₂✝) ⟶b y₂ → Bexp.bool (decide (n₁✝ ≠ n₂✝)) = y₂leLeft x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝y₂:Bexp⊢ Bexp.le a₁✝ a₂✝ ⟶b y₂ → Bexp.le a₁'✝ a₂✝ = y₂leRight x:Bexpy₁:Bexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝y₂:Bexp⊢ Bexp.le v₁✝ a₂✝ ⟶b y₂ → Bexp.le v₁✝ a₂'✝ = y₂le x:Bexpy₁:Bexpn₁✝:Natn₂✝:Naty₂:Bexp⊢ Bexp.le (Aexp.num n₁✝) (Aexp.num n₂✝) ⟶b y₂ → Bexp.bool (decide (n₁✝ ≤ n₂✝)) = y₂gtLeft x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝y₂:Bexp⊢ Bexp.gt a₁✝ a₂✝ ⟶b y₂ → Bexp.gt a₁'✝ a₂✝ = y₂gtRight x:Bexpy₁:Bexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝y₂:Bexp⊢ Bexp.gt v₁✝ a₂✝ ⟶b y₂ → Bexp.gt v₁✝ a₂'✝ = y₂gt x:Bexpy₁:Bexpn₁✝:Natn₂✝:Naty₂:Bexp⊢ Bexp.gt (Aexp.num n₁✝) (Aexp.num n₂✝) ⟶b y₂ → Bexp.bool (decide (n₁✝ > n₂✝)) = y₂notStep x:Bexpy₁:Bexpb₁✝:Bexpb₁'✝:Bexph✝:b₁✝ ⟶b b₁'✝h_ih✝:∀ (y₂ : Bexp), b₁✝ ⟶b y₂ → b₁'✝ = y₂y₂:Bexp⊢ b₁✝.not ⟶b y₂ → b₁'✝.not = y₂notTrue x:Bexpy₁:Bexpy₂:Bexp⊢ (Bexp.bool true).not ⟶b y₂ → Bexp.bool false = y₂notFalse x:Bexpy₁:Bexpy₂:Bexp⊢ (Bexp.bool false).not ⟶b y₂ → Bexp.bool true = y₂andStep x:Bexpy₁:Bexpb₁✝:Bexpb₁'✝:Bexpb₂✝:Bexph✝:b₁✝ ⟶b b₁'✝h_ih✝:∀ (y₂ : Bexp), b₁✝ ⟶b y₂ → b₁'✝ = y₂y₂:Bexp⊢ b₁✝.and b₂✝ ⟶b y₂ → b₁'✝.and b₂✝ = y₂andTrueStep x:Bexpy₁:Bexpb₂✝:Bexpb₂'✝:Bexph✝:b₂✝ ⟶b b₂'✝h_ih✝:∀ (y₂ : Bexp), b₂✝ ⟶b y₂ → b₂'✝ = y₂y₂:Bexp⊢ (Bexp.bool true).and b₂✝ ⟶b y₂ → (Bexp.bool true).and b₂'✝ = y₂andFalse x:Bexpy₁:Bexpb₂✝:Bexpy₂:Bexp⊢ (Bexp.bool false).and b₂✝ ⟶b y₂ → Bexp.bool false = y₂andTrueTrue x:Bexpy₁:Bexpy₂:Bexp⊢ (Bexp.bool true).and (Bexp.bool true) ⟶b y₂ → Bexp.bool true = y₂andTrueFalse x:Bexpy₁:Bexpy₂:Bexp⊢ (Bexp.bool true).and (Bexp.bool false) ⟶b y₂ → Bexp.bool false = y₂ <;> eqLeft x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝y₂:Bexp⊢ Bexp.eq a₁✝ a₂✝ ⟶b y₂ → Bexp.eq a₁'✝ a₂✝ = y₂eqRight x:Bexpy₁:Bexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝y₂:Bexp⊢ Bexp.eq v₁✝ a₂✝ ⟶b y₂ → Bexp.eq v₁✝ a₂'✝ = y₂eq x:Bexpy₁:Bexpn₁✝:Natn₂✝:Naty₂:Bexp⊢ Bexp.eq (Aexp.num n₁✝) (Aexp.num n₂✝) ⟶b y₂ → Bexp.bool (decide (n₁✝ = n₂✝)) = y₂neqLeft x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝y₂:Bexp⊢ Bexp.neq a₁✝ a₂✝ ⟶b y₂ → Bexp.neq a₁'✝ a₂✝ = y₂neqRight x:Bexpy₁:Bexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝y₂:Bexp⊢ Bexp.neq v₁✝ a₂✝ ⟶b y₂ → Bexp.neq v₁✝ a₂'✝ = y₂neq x:Bexpy₁:Bexpn₁✝:Natn₂✝:Naty₂:Bexp⊢ Bexp.neq (Aexp.num n₁✝) (Aexp.num n₂✝) ⟶b y₂ → Bexp.bool (decide (n₁✝ ≠ n₂✝)) = y₂leLeft x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝y₂:Bexp⊢ Bexp.le a₁✝ a₂✝ ⟶b y₂ → Bexp.le a₁'✝ a₂✝ = y₂leRight x:Bexpy₁:Bexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝y₂:Bexp⊢ Bexp.le v₁✝ a₂✝ ⟶b y₂ → Bexp.le v₁✝ a₂'✝ = y₂le x:Bexpy₁:Bexpn₁✝:Natn₂✝:Naty₂:Bexp⊢ Bexp.le (Aexp.num n₁✝) (Aexp.num n₂✝) ⟶b y₂ → Bexp.bool (decide (n₁✝ ≤ n₂✝)) = y₂gtLeft x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝y₂:Bexp⊢ Bexp.gt a₁✝ a₂✝ ⟶b y₂ → Bexp.gt a₁'✝ a₂✝ = y₂gtRight x:Bexpy₁:Bexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝y₂:Bexp⊢ Bexp.gt v₁✝ a₂✝ ⟶b y₂ → Bexp.gt v₁✝ a₂'✝ = y₂gt x:Bexpy₁:Bexpn₁✝:Natn₂✝:Naty₂:Bexp⊢ Bexp.gt (Aexp.num n₁✝) (Aexp.num n₂✝) ⟶b y₂ → Bexp.bool (decide (n₁✝ > n₂✝)) = y₂notStep x:Bexpy₁:Bexpb₁✝:Bexpb₁'✝:Bexph✝:b₁✝ ⟶b b₁'✝h_ih✝:∀ (y₂ : Bexp), b₁✝ ⟶b y₂ → b₁'✝ = y₂y₂:Bexp⊢ b₁✝.not ⟶b y₂ → b₁'✝.not = y₂notTrue x:Bexpy₁:Bexpy₂:Bexp⊢ (Bexp.bool true).not ⟶b y₂ → Bexp.bool false = y₂notFalse x:Bexpy₁:Bexpy₂:Bexp⊢ (Bexp.bool false).not ⟶b y₂ → Bexp.bool true = y₂andStep x:Bexpy₁:Bexpb₁✝:Bexpb₁'✝:Bexpb₂✝:Bexph✝:b₁✝ ⟶b b₁'✝h_ih✝:∀ (y₂ : Bexp), b₁✝ ⟶b y₂ → b₁'✝ = y₂y₂:Bexp⊢ b₁✝.and b₂✝ ⟶b y₂ → b₁'✝.and b₂✝ = y₂andTrueStep x:Bexpy₁:Bexpb₂✝:Bexpb₂'✝:Bexph✝:b₂✝ ⟶b b₂'✝h_ih✝:∀ (y₂ : Bexp), b₂✝ ⟶b y₂ → b₂'✝ = y₂y₂:Bexp⊢ (Bexp.bool true).and b₂✝ ⟶b y₂ → (Bexp.bool true).and b₂'✝ = y₂andFalse x:Bexpy₁:Bexpb₂✝:Bexpy₂:Bexp⊢ (Bexp.bool false).and b₂✝ ⟶b y₂ → Bexp.bool false = y₂andTrueTrue x:Bexpy₁:Bexpy₂:Bexp⊢ (Bexp.bool true).and (Bexp.bool true) ⟶b y₂ → Bexp.bool true = y₂andTrueFalse x:Bexpy₁:Bexpy₂:Bexp⊢ (Bexp.bool true).and (Bexp.bool false) ⟶b y₂ → Bexp.bool false = y₂ intro h₂ andTrueFalse x:Bexpy₁:Bexpy₂:Bexph₂:(Bexp.bool true).and (Bexp.bool false) ⟶b y₂⊢ Bexp.bool false = y₂ <;> eqLeft x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝y₂:Bexph₂:Bexp.eq a₁✝ a₂✝ ⟶b y₂⊢ Bexp.eq a₁'✝ a₂✝ = y₂eqRight x:Bexpy₁:Bexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝y₂:Bexph₂:Bexp.eq v₁✝ a₂✝ ⟶b y₂⊢ Bexp.eq v₁✝ a₂'✝ = y₂eq x:Bexpy₁:Bexpn₁✝:Natn₂✝:Naty₂:Bexph₂:Bexp.eq (Aexp.num n₁✝) (Aexp.num n₂✝) ⟶b y₂⊢ Bexp.bool (decide (n₁✝ = n₂✝)) = y₂neqLeft x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝y₂:Bexph₂:Bexp.neq a₁✝ a₂✝ ⟶b y₂⊢ Bexp.neq a₁'✝ a₂✝ = y₂neqRight x:Bexpy₁:Bexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝y₂:Bexph₂:Bexp.neq v₁✝ a₂✝ ⟶b y₂⊢ Bexp.neq v₁✝ a₂'✝ = y₂neq x:Bexpy₁:Bexpn₁✝:Natn₂✝:Naty₂:Bexph₂:Bexp.neq (Aexp.num n₁✝) (Aexp.num n₂✝) ⟶b y₂⊢ Bexp.bool (decide (n₁✝ ≠ n₂✝)) = y₂leLeft x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝y₂:Bexph₂:Bexp.le a₁✝ a₂✝ ⟶b y₂⊢ Bexp.le a₁'✝ a₂✝ = y₂leRight x:Bexpy₁:Bexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝y₂:Bexph₂:Bexp.le v₁✝ a₂✝ ⟶b y₂⊢ Bexp.le v₁✝ a₂'✝ = y₂le x:Bexpy₁:Bexpn₁✝:Natn₂✝:Naty₂:Bexph₂:Bexp.le (Aexp.num n₁✝) (Aexp.num n₂✝) ⟶b y₂⊢ Bexp.bool (decide (n₁✝ ≤ n₂✝)) = y₂gtLeft x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝y₂:Bexph₂:Bexp.gt a₁✝ a₂✝ ⟶b y₂⊢ Bexp.gt a₁'✝ a₂✝ = y₂gtRight x:Bexpy₁:Bexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝y₂:Bexph₂:Bexp.gt v₁✝ a₂✝ ⟶b y₂⊢ Bexp.gt v₁✝ a₂'✝ = y₂gt x:Bexpy₁:Bexpn₁✝:Natn₂✝:Naty₂:Bexph₂:Bexp.gt (Aexp.num n₁✝) (Aexp.num n₂✝) ⟶b y₂⊢ Bexp.bool (decide (n₁✝ > n₂✝)) = y₂notStep x:Bexpy₁:Bexpb₁✝:Bexpb₁'✝:Bexph✝:b₁✝ ⟶b b₁'✝h_ih✝:∀ (y₂ : Bexp), b₁✝ ⟶b y₂ → b₁'✝ = y₂y₂:Bexph₂:b₁✝.not ⟶b y₂⊢ b₁'✝.not = y₂notTrue x:Bexpy₁:Bexpy₂:Bexph₂:(Bexp.bool true).not ⟶b y₂⊢ Bexp.bool false = y₂notFalse x:Bexpy₁:Bexpy₂:Bexph₂:(Bexp.bool false).not ⟶b y₂⊢ Bexp.bool true = y₂andStep x:Bexpy₁:Bexpb₁✝:Bexpb₁'✝:Bexpb₂✝:Bexph✝:b₁✝ ⟶b b₁'✝h_ih✝:∀ (y₂ : Bexp), b₁✝ ⟶b y₂ → b₁'✝ = y₂y₂:Bexph₂:b₁✝.and b₂✝ ⟶b y₂⊢ b₁'✝.and b₂✝ = y₂andTrueStep x:Bexpy₁:Bexpb₂✝:Bexpb₂'✝:Bexph✝:b₂✝ ⟶b b₂'✝h_ih✝:∀ (y₂ : Bexp), b₂✝ ⟶b y₂ → b₂'✝ = y₂y₂:Bexph₂:(Bexp.bool true).and b₂✝ ⟶b y₂⊢ (Bexp.bool true).and b₂'✝ = y₂andFalse x:Bexpy₁:Bexpb₂✝:Bexpy₂:Bexph₂:(Bexp.bool false).and b₂✝ ⟶b y₂⊢ Bexp.bool false = y₂andTrueTrue x:Bexpy₁:Bexpy₂:Bexph₂:(Bexp.bool true).and (Bexp.bool true) ⟶b y₂⊢ Bexp.bool true = y₂andTrueFalse x:Bexpy₁:Bexpy₂:Bexph₂:(Bexp.bool true).and (Bexp.bool false) ⟶b y₂⊢ Bexp.bool false = y₂ cases h₂ andTrueFalse.andStep x:Bexpy₁:Bexpb₁'✝:Bexph✝:Bexp.bool true ⟶b b₁'✝⊢ Bexp.bool false = b₁'✝.and (Bexp.bool false)andTrueFalse.andTrueStep x:Bexpy₁:Bexpb₂'✝:Bexph✝:Bexp.bool false ⟶b b₂'✝⊢ Bexp.bool false = (Bexp.bool true).and b₂'✝andTrueFalse.andTrueFalse x:Bexpy₁:Bexp⊢ Bexp.bool false = Bexp.bool false <;> eqLeft.eqLeft x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝¹:Aexpa₂✝:Aexph✝¹:a₁✝ ⟶a a₁'✝a₁'✝:Aexph✝:a₁✝ ⟶a a₁'✝⊢ Bexp.eq a₁'✝¹ a₂✝ = Bexp.eq a₁'✝ a₂✝eqLeft.eqRight x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝¹:a₁✝ ⟶a a₁'✝a₂'✝:Aexphv✝:IsAValue a₁✝h✝:a₂✝ ⟶a a₂'✝⊢ Bexp.eq a₁'✝ a₂✝ = Bexp.eq a₁✝ a₂'✝eqLeft.eq x:Bexpy₁:Bexpa₁'✝:Aexpn₁✝:Natn₂✝:Nath✝:Aexp.num n₁✝ ⟶a a₁'✝⊢ Bexp.eq a₁'✝ (Aexp.num n₂✝) = Bexp.bool (decide (n₁✝ = n₂✝))eqRight.eqLeft x:Bexpy₁:Bexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝¹:a₂✝ ⟶a a₂'✝a₁'✝:Aexph✝:v₁✝ ⟶a a₁'✝⊢ Bexp.eq v₁✝ a₂'✝ = Bexp.eq a₁'✝ a₂✝eqRight.eqRight x:Bexpy₁:Bexpv₁✝:Aexpa₂✝:Aexpa₂'✝¹:Aexphv✝¹:IsAValue v₁✝h✝¹:a₂✝ ⟶a a₂'✝a₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝⊢ Bexp.eq v₁✝ a₂'✝¹ = Bexp.eq v₁✝ a₂'✝eqRight.eq x:Bexpy₁:Bexpa₂'✝:Aexpn₁✝:Natn₂✝:Nathv✝:IsAValue (Aexp.num n₁✝)h✝:Aexp.num n₂✝ ⟶a a₂'✝⊢ Bexp.eq (Aexp.num n₁✝) a₂'✝ = Bexp.bool (decide (n₁✝ = n₂✝))eq.eqLeft x:Bexpy₁:Bexpn₁✝:Natn₂✝:Nata₁'✝:Aexph✝:Aexp.num n₁✝ ⟶a a₁'✝⊢ Bexp.bool (decide (n₁✝ = n₂✝)) = Bexp.eq a₁'✝ (Aexp.num n₂✝)eq.eqRight x:Bexpy₁:Bexpn₁✝:Natn₂✝:Nata₂'✝:Aexphv✝:IsAValue (Aexp.num n₁✝)h✝:Aexp.num n₂✝ ⟶a a₂'✝⊢ Bexp.bool (decide (n₁✝ = n₂✝)) = Bexp.eq (Aexp.num n₁✝) a₂'✝eq.eq x:Bexpy₁:Bexpn₁✝:Natn₂✝:Nat⊢ Bexp.bool (decide (n₁✝ = n₂✝)) = Bexp.bool (decide (n₁✝ = n₂✝))neqLeft.neqLeft x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝¹:Aexpa₂✝:Aexph✝¹:a₁✝ ⟶a a₁'✝a₁'✝:Aexph✝:a₁✝ ⟶a a₁'✝⊢ Bexp.neq a₁'✝¹ a₂✝ = Bexp.neq a₁'✝ a₂✝neqLeft.neqRight x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝¹:a₁✝ ⟶a a₁'✝a₂'✝:Aexphv✝:IsAValue a₁✝h✝:a₂✝ ⟶a a₂'✝⊢ Bexp.neq a₁'✝ a₂✝ = Bexp.neq a₁✝ a₂'✝neqLeft.neq x:Bexpy₁:Bexpa₁'✝:Aexpn₁✝:Natn₂✝:Nath✝:Aexp.num n₁✝ ⟶a a₁'✝⊢ Bexp.neq a₁'✝ (Aexp.num n₂✝) = Bexp.bool (decide (n₁✝ ≠ n₂✝))neqRight.neqLeft x:Bexpy₁:Bexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝¹:a₂✝ ⟶a a₂'✝a₁'✝:Aexph✝:v₁✝ ⟶a a₁'✝⊢ Bexp.neq v₁✝ a₂'✝ = Bexp.neq a₁'✝ a₂✝neqRight.neqRight x:Bexpy₁:Bexpv₁✝:Aexpa₂✝:Aexpa₂'✝¹:Aexphv✝¹:IsAValue v₁✝h✝¹:a₂✝ ⟶a a₂'✝a₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝⊢ Bexp.neq v₁✝ a₂'✝¹ = Bexp.neq v₁✝ a₂'✝neqRight.neq x:Bexpy₁:Bexpa₂'✝:Aexpn₁✝:Natn₂✝:Nathv✝:IsAValue (Aexp.num n₁✝)h✝:Aexp.num n₂✝ ⟶a a₂'✝⊢ Bexp.neq (Aexp.num n₁✝) a₂'✝ = Bexp.bool (decide (n₁✝ ≠ n₂✝))neq.neqLeft x:Bexpy₁:Bexpn₁✝:Natn₂✝:Nata₁'✝:Aexph✝:Aexp.num n₁✝ ⟶a a₁'✝⊢ Bexp.bool (decide (n₁✝ ≠ n₂✝)) = Bexp.neq a₁'✝ (Aexp.num n₂✝)neq.neqRight x:Bexpy₁:Bexpn₁✝:Natn₂✝:Nata₂'✝:Aexphv✝:IsAValue (Aexp.num n₁✝)h✝:Aexp.num n₂✝ ⟶a a₂'✝⊢ Bexp.bool (decide (n₁✝ ≠ n₂✝)) = Bexp.neq (Aexp.num n₁✝) a₂'✝neq.neq x:Bexpy₁:Bexpn₁✝:Natn₂✝:Nat⊢ Bexp.bool (decide (n₁✝ ≠ n₂✝)) = Bexp.bool (decide (n₁✝ ≠ n₂✝))leLeft.leLeft x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝¹:Aexpa₂✝:Aexph✝¹:a₁✝ ⟶a a₁'✝a₁'✝:Aexph✝:a₁✝ ⟶a a₁'✝⊢ Bexp.le a₁'✝¹ a₂✝ = Bexp.le a₁'✝ a₂✝leLeft.leRight x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝¹:a₁✝ ⟶a a₁'✝a₂'✝:Aexphv✝:IsAValue a₁✝h✝:a₂✝ ⟶a a₂'✝⊢ Bexp.le a₁'✝ a₂✝ = Bexp.le a₁✝ a₂'✝leLeft.le x:Bexpy₁:Bexpa₁'✝:Aexpn₁✝:Natn₂✝:Nath✝:Aexp.num n₁✝ ⟶a a₁'✝⊢ Bexp.le a₁'✝ (Aexp.num n₂✝) = Bexp.bool (decide (n₁✝ ≤ n₂✝))leRight.leLeft x:Bexpy₁:Bexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝¹:a₂✝ ⟶a a₂'✝a₁'✝:Aexph✝:v₁✝ ⟶a a₁'✝⊢ Bexp.le v₁✝ a₂'✝ = Bexp.le a₁'✝ a₂✝leRight.leRight x:Bexpy₁:Bexpv₁✝:Aexpa₂✝:Aexpa₂'✝¹:Aexphv✝¹:IsAValue v₁✝h✝¹:a₂✝ ⟶a a₂'✝a₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝⊢ Bexp.le v₁✝ a₂'✝¹ = Bexp.le v₁✝ a₂'✝leRight.le x:Bexpy₁:Bexpa₂'✝:Aexpn₁✝:Natn₂✝:Nathv✝:IsAValue (Aexp.num n₁✝)h✝:Aexp.num n₂✝ ⟶a a₂'✝⊢ Bexp.le (Aexp.num n₁✝) a₂'✝ = Bexp.bool (decide (n₁✝ ≤ n₂✝))le.leLeft x:Bexpy₁:Bexpn₁✝:Natn₂✝:Nata₁'✝:Aexph✝:Aexp.num n₁✝ ⟶a a₁'✝⊢ Bexp.bool (decide (n₁✝ ≤ n₂✝)) = Bexp.le a₁'✝ (Aexp.num n₂✝)le.leRight x:Bexpy₁:Bexpn₁✝:Natn₂✝:Nata₂'✝:Aexphv✝:IsAValue (Aexp.num n₁✝)h✝:Aexp.num n₂✝ ⟶a a₂'✝⊢ Bexp.bool (decide (n₁✝ ≤ n₂✝)) = Bexp.le (Aexp.num n₁✝) a₂'✝le.le x:Bexpy₁:Bexpn₁✝:Natn₂✝:Nat⊢ Bexp.bool (decide (n₁✝ ≤ n₂✝)) = Bexp.bool (decide (n₁✝ ≤ n₂✝))gtLeft.gtLeft x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝¹:Aexpa₂✝:Aexph✝¹:a₁✝ ⟶a a₁'✝a₁'✝:Aexph✝:a₁✝ ⟶a a₁'✝⊢ Bexp.gt a₁'✝¹ a₂✝ = Bexp.gt a₁'✝ a₂✝gtLeft.gtRight x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝¹:a₁✝ ⟶a a₁'✝a₂'✝:Aexphv✝:IsAValue a₁✝h✝:a₂✝ ⟶a a₂'✝⊢ Bexp.gt a₁'✝ a₂✝ = Bexp.gt a₁✝ a₂'✝gtLeft.gt x:Bexpy₁:Bexpa₁'✝:Aexpn₁✝:Natn₂✝:Nath✝:Aexp.num n₁✝ ⟶a a₁'✝⊢ Bexp.gt a₁'✝ (Aexp.num n₂✝) = Bexp.bool (decide (n₁✝ > n₂✝))gtRight.gtLeft x:Bexpy₁:Bexpv₁✝:Aexpa₂✝:Aexpa₂'✝:Aexphv✝:IsAValue v₁✝h✝¹:a₂✝ ⟶a a₂'✝a₁'✝:Aexph✝:v₁✝ ⟶a a₁'✝⊢ Bexp.gt v₁✝ a₂'✝ = Bexp.gt a₁'✝ a₂✝gtRight.gtRight x:Bexpy₁:Bexpv₁✝:Aexpa₂✝:Aexpa₂'✝¹:Aexphv✝¹:IsAValue v₁✝h✝¹:a₂✝ ⟶a a₂'✝a₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝⊢ Bexp.gt v₁✝ a₂'✝¹ = Bexp.gt v₁✝ a₂'✝gtRight.gt x:Bexpy₁:Bexpa₂'✝:Aexpn₁✝:Natn₂✝:Nathv✝:IsAValue (Aexp.num n₁✝)h✝:Aexp.num n₂✝ ⟶a a₂'✝⊢ Bexp.gt (Aexp.num n₁✝) a₂'✝ = Bexp.bool (decide (n₁✝ > n₂✝))gt.gtLeft x:Bexpy₁:Bexpn₁✝:Natn₂✝:Nata₁'✝:Aexph✝:Aexp.num n₁✝ ⟶a a₁'✝⊢ Bexp.bool (decide (n₁✝ > n₂✝)) = Bexp.gt a₁'✝ (Aexp.num n₂✝)gt.gtRight x:Bexpy₁:Bexpn₁✝:Natn₂✝:Nata₂'✝:Aexphv✝:IsAValue (Aexp.num n₁✝)h✝:Aexp.num n₂✝ ⟶a a₂'✝⊢ Bexp.bool (decide (n₁✝ > n₂✝)) = Bexp.gt (Aexp.num n₁✝) a₂'✝gt.gt x:Bexpy₁:Bexpn₁✝:Natn₂✝:Nat⊢ Bexp.bool (decide (n₁✝ > n₂✝)) = Bexp.bool (decide (n₁✝ > n₂✝))notStep.notStep x:Bexpy₁:Bexpb₁✝:Bexpb₁'✝¹:Bexph✝¹:b₁✝ ⟶b b₁'✝h_ih✝:∀ (y₂ : Bexp), b₁✝ ⟶b y₂ → b₁'✝ = y₂b₁'✝:Bexph✝:b₁✝ ⟶b b₁'✝⊢ b₁'✝¹.not = b₁'✝.notnotStep.notTrue x:Bexpy₁:Bexpb₁'✝:Bexph✝:Bexp.bool true ⟶b b₁'✝h_ih✝:∀ (y₂ : Bexp), Bexp.bool true ⟶b y₂ → b₁'✝ = y₂⊢ b₁'✝.not = Bexp.bool falsenotStep.notFalse x:Bexpy₁:Bexpb₁'✝:Bexph✝:Bexp.bool false ⟶b b₁'✝h_ih✝:∀ (y₂ : Bexp), Bexp.bool false ⟶b y₂ → b₁'✝ = y₂⊢ b₁'✝.not = Bexp.bool truenotTrue.notStep x:Bexpy₁:Bexpb₁'✝:Bexph✝:Bexp.bool true ⟶b b₁'✝⊢ Bexp.bool false = b₁'✝.notnotTrue.notTrue x:Bexpy₁:Bexp⊢ Bexp.bool false = Bexp.bool falsenotFalse.notStep x:Bexpy₁:Bexpb₁'✝:Bexph✝:Bexp.bool false ⟶b b₁'✝⊢ Bexp.bool true = b₁'✝.notnotFalse.notFalse x:Bexpy₁:Bexp⊢ Bexp.bool true = Bexp.bool trueandStep.andStep x:Bexpy₁:Bexpb₁✝:Bexpb₁'✝¹:Bexpb₂✝:Bexph✝¹:b₁✝ ⟶b b₁'✝h_ih✝:∀ (y₂ : Bexp), b₁✝ ⟶b y₂ → b₁'✝ = y₂b₁'✝:Bexph✝:b₁✝ ⟶b b₁'✝⊢ b₁'✝¹.and b₂✝ = b₁'✝.and b₂✝andStep.andTrueStep x:Bexpy₁:Bexpb₁'✝:Bexpb₂✝:Bexpb₂'✝:Bexph✝¹:Bexp.bool true ⟶b b₁'✝h_ih✝:∀ (y₂ : Bexp), Bexp.bool true ⟶b y₂ → b₁'✝ = y₂h✝:b₂✝ ⟶b b₂'✝⊢ b₁'✝.and b₂✝ = (Bexp.bool true).and b₂'✝andStep.andFalse x:Bexpy₁:Bexpb₁'✝:Bexpb₂✝:Bexph✝:Bexp.bool false ⟶b b₁'✝h_ih✝:∀ (y₂ : Bexp), Bexp.bool false ⟶b y₂ → b₁'✝ = y₂⊢ b₁'✝.and b₂✝ = Bexp.bool falseandStep.andTrueTrue x:Bexpy₁:Bexpb₁'✝:Bexph✝:Bexp.bool true ⟶b b₁'✝h_ih✝:∀ (y₂ : Bexp), Bexp.bool true ⟶b y₂ → b₁'✝ = y₂⊢ b₁'✝.and (Bexp.bool true) = Bexp.bool trueandStep.andTrueFalse x:Bexpy₁:Bexpb₁'✝:Bexph✝:Bexp.bool true ⟶b b₁'✝h_ih✝:∀ (y₂ : Bexp), Bexp.bool true ⟶b y₂ → b₁'✝ = y₂⊢ b₁'✝.and (Bexp.bool false) = Bexp.bool falseandTrueStep.andStep x:Bexpy₁:Bexpb₂✝:Bexpb₂'✝:Bexph✝¹:b₂✝ ⟶b b₂'✝h_ih✝:∀ (y₂ : Bexp), b₂✝ ⟶b y₂ → b₂'✝ = y₂b₁'✝:Bexph✝:Bexp.bool true ⟶b b₁'✝⊢ (Bexp.bool true).and b₂'✝ = b₁'✝.and b₂✝andTrueStep.andTrueStep x:Bexpy₁:Bexpb₂✝:Bexpb₂'✝¹:Bexph✝¹:b₂✝ ⟶b b₂'✝h_ih✝:∀ (y₂ : Bexp), b₂✝ ⟶b y₂ → b₂'✝ = y₂b₂'✝:Bexph✝:b₂✝ ⟶b b₂'✝⊢ (Bexp.bool true).and b₂'✝¹ = (Bexp.bool true).and b₂'✝andTrueStep.andTrueTrue x:Bexpy₁:Bexpb₂'✝:Bexph✝:Bexp.bool true ⟶b b₂'✝h_ih✝:∀ (y₂ : Bexp), Bexp.bool true ⟶b y₂ → b₂'✝ = y₂⊢ (Bexp.bool true).and b₂'✝ = Bexp.bool trueandTrueStep.andTrueFalse x:Bexpy₁:Bexpb₂'✝:Bexph✝:Bexp.bool false ⟶b b₂'✝h_ih✝:∀ (y₂ : Bexp), Bexp.bool false ⟶b y₂ → b₂'✝ = y₂⊢ (Bexp.bool true).and b₂'✝ = Bexp.bool falseandFalse.andStep x:Bexpy₁:Bexpb₂✝:Bexpb₁'✝:Bexph✝:Bexp.bool false ⟶b b₁'✝⊢ Bexp.bool false = b₁'✝.and b₂✝andFalse.andFalse x:Bexpy₁:Bexpb₂✝:Bexp⊢ Bexp.bool false = Bexp.bool falseandTrueTrue.andStep x:Bexpy₁:Bexpb₁'✝:Bexph✝:Bexp.bool true ⟶b b₁'✝⊢ Bexp.bool true = b₁'✝.and (Bexp.bool true)andTrueTrue.andTrueStep x:Bexpy₁:Bexpb₂'✝:Bexph✝:Bexp.bool true ⟶b b₂'✝⊢ Bexp.bool true = (Bexp.bool true).and b₂'✝andTrueTrue.andTrueTrue x:Bexpy₁:Bexp⊢ Bexp.bool true = Bexp.bool trueandTrueFalse.andStep x:Bexpy₁:Bexpb₁'✝:Bexph✝:Bexp.bool true ⟶b b₁'✝⊢ Bexp.bool false = b₁'✝.and (Bexp.bool false)andTrueFalse.andTrueStep x:Bexpy₁:Bexpb₂'✝:Bexph✝:Bexp.bool false ⟶b b₂'✝⊢ Bexp.bool false = (Bexp.bool true).and b₂'✝andTrueFalse.andTrueFalse x:Bexpy₁:Bexp⊢ Bexp.bool false = Bexp.bool false
first
| rfl All goals completed! 🐙
| cases ‹AStep (Aexp.num _) _› andTrueFalse.andTrueStep x:Bexpy₁:Bexpb₂'✝:Bexph✝:Bexp.bool false ⟶b b₂'✝⊢ Bexp.bool false = (Bexp.bool true).and b₂'✝
| (cases ‹IsAValue _› andTrueFalse.andTrueStep x:Bexpy₁:Bexpb₂'✝:Bexph✝:Bexp.bool false ⟶b b₂'✝⊢ Bexp.bool false = (Bexp.bool true).and b₂'✝; cases ‹AStep (Aexp.num _) _› gtRight.gtRight.num x:Bexpy₁:Bexpa₂✝:Aexpa₂'✝¹:Aexph✝¹:a₂✝ ⟶a a₂'✝a₂'✝:Aexph✝:a₂✝ ⟶a a₂'✝n✝:Nathv✝:IsAValue (Aexp.num n✝)⊢ Bexp.gt (Aexp.num n✝) a₂'✝¹ = Bexp.gt (Aexp.num n✝) a₂'✝)
| cases ‹BStep (Bexp.bool _) _› All goals completed! 🐙
| (congr 1 andTrueStep.andTrueStep.e_b₂ x:Bexpy₁:Bexpb₂✝:Bexpb₂'✝¹:Bexph✝¹:b₂✝ ⟶b b₂'✝h_ih✝:∀ (y₂ : Bexp), b₂✝ ⟶b y₂ → b₂'✝ = y₂b₂'✝:Bexph✝:b₂✝ ⟶b b₂'✝⊢ b₂'✝¹ = b₂'✝ <;> andTrueStep.andTrueStep.e_b₂ x:Bexpy₁:Bexpb₂✝:Bexpb₂'✝¹:Bexph✝¹:b₂✝ ⟶b b₂'✝h_ih✝:∀ (y₂ : Bexp), b₂✝ ⟶b y₂ → b₂'✝ = y₂b₂'✝:Bexph✝:b₂✝ ⟶b b₂'✝⊢ b₂'✝¹ = b₂'✝ first | rfl andTrueStep.andTrueStep.e_b₂ x:Bexpy₁:Bexpb₂✝:Bexpb₂'✝¹:Bexph✝¹:b₂✝ ⟶b b₂'✝h_ih✝:∀ (y₂ : Bexp), b₂✝ ⟶b y₂ → b₂'✝ = y₂b₂'✝:Bexph✝:b₂✝ ⟶b b₂'✝⊢ b₂'✝¹ = b₂'✝ | (apply astep_deterministic andTrueStep.andTrueStep.e_b₂ x:Bexpy₁:Bexpb₂✝:Bexpb₂'✝¹:Bexph✝¹:b₂✝ ⟶b b₂'✝h_ih✝:∀ (y₂ : Bexp), b₂✝ ⟶b y₂ → b₂'✝ = y₂b₂'✝:Bexph✝:b₂✝ ⟶b b₂'✝⊢ b₂'✝¹ = b₂'✝ <;> gtRight.gtRight.e_a₂.a x:Bexpy₁:Bexpv₁✝:Aexpa₂✝:Aexpa₂'✝¹:Aexphv✝¹:IsAValue v₁✝h✝¹:a₂✝ ⟶a a₂'✝a₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝⊢ ?gtRight.gtRight.e_a₂.x ⟶a a₂'✝¹gtRight.gtRight.e_a₂.a x:Bexpy₁:Bexpv₁✝:Aexpa₂✝:Aexpa₂'✝¹:Aexphv✝¹:IsAValue v₁✝h✝¹:a₂✝ ⟶a a₂'✝a₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝⊢ ?gtRight.gtRight.e_a₂.x ⟶a a₂'✝gtRight.gtRight.e_a₂.x x:Bexpy₁:Bexpv₁✝:Aexpa₂✝:Aexpa₂'✝¹:Aexphv✝¹:IsAValue v₁✝h✝¹:a₂✝ ⟶a a₂'✝a₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝⊢ Aexp assumption All goals completed! 🐙) | (apply ‹∀ _, BStep _ _ → _ = _› andTrueStep.andTrueStep.e_b₂ x:Bexpy₁:Bexpb₂✝:Bexpb₂'✝¹:Bexph✝¹:b₂✝ ⟶b b₂'✝h_ih✝:∀ (y₂ : Bexp), b₂✝ ⟶b y₂ → b₂'✝ = y₂b₂'✝:Bexph✝:b₂✝ ⟶b b₂'✝⊢ b₂✝ ⟶b b₂'✝ <;> andTrueStep.andTrueStep.e_b₂ x:Bexpy₁:Bexpb₂✝:Bexpb₂'✝¹:Bexph✝¹:b₂✝ ⟶b b₂'✝h_ih✝:∀ (y₂ : Bexp), b₂✝ ⟶b y₂ → b₂'✝ = y₂b₂'✝:Bexph✝:b₂✝ ⟶b b₂'✝⊢ b₂✝ ⟶b b₂'✝ assumption All goals completed! 🐙))
3.5.3. Nondeterministic Evaluation
The relation ⟶a above bakes in a left-to-right evaluation order: the rule
plusRight can fire only once the left operand is already a value (IsAValue v₁).
But nothing about the meaning of + requires that order — we could just as
well reduce the right operand first, or interleave the two. Different orders
are exactly what a concurrent or optimizing implementation might choose, so it
is natural to ask whether the choice can affect the final answer.
Let's find out. We define a second small-step relation, ⟶n, that is
identical to ⟶a except that we drop the IsAValue side-condition: either
operand may take a step at any time.
inductive ANStep : Aexp → Aexp → Prop where
| plusLeft (a₁ a₁' a₂ : Aexp) (h : ANStep a₁ a₁') : ANStep (.plus a₁ a₂) (.plus a₁' a₂)
| plusRight (a₁ a₂ a₂' : Aexp) (h : ANStep a₂ a₂') : ANStep (.plus a₁ a₂) (.plus a₁ a₂')
| plus (n₁ n₂ : Nat) : ANStep (.plus (.num n₁) (.num n₂)) (.num (n₁ + n₂))
| minusLeft (a₁ a₁' a₂ : Aexp) (h : ANStep a₁ a₁') : ANStep (.minus a₁ a₂) (.minus a₁' a₂)
| minusRight (a₁ a₂ a₂' : Aexp) (h : ANStep a₂ a₂') : ANStep (.minus a₁ a₂) (.minus a₁ a₂')
| minus (n₁ n₂ : Nat) : ANStep (.minus (.num n₁) (.num n₂)) (.num (n₁ - n₂))
| multLeft (a₁ a₁' a₂ : Aexp) (h : ANStep a₁ a₁') : ANStep (.mult a₁ a₂) (.mult a₁' a₂)
| multRight (a₁ a₂ a₂' : Aexp) (h : ANStep a₂ a₂') : ANStep (.mult a₁ a₂) (.mult a₁ a₂')
| mult (n₁ n₂ : Nat) : ANStep (.mult (.num n₁) (.num n₂)) (.num (n₁ * n₂))
scoped notation:40 a:41 " ⟶n " a':41 => ANStep a a'
Unlike ⟶a, this relation really is nondeterministic: a single term can step
in two different ways, depending on which operand we choose to advance.
theorem anstep_not_deterministic : ¬ Deterministic ANStep := by ⊢ ¬Deterministic ANStep
intro hd hd:Deterministic ANStep⊢ False
have s₁ : ANStep (.plus (.plus (.num 1) (.num 1)) (.plus (.num 2) (.num 2)))
(.plus (.num 2) (.plus (.num 2) (.num 2))) :=
.plusLeft _ _ _ (.plus 1 1) hd:Deterministic ANSteps₁:((Aexp.num 1).plus (Aexp.num 1)).plus ((Aexp.num 2).plus (Aexp.num 2)) ⟶n
(Aexp.num 2).plus ((Aexp.num 2).plus (Aexp.num 2))⊢ False
have s₂ : ANStep (.plus (.plus (.num 1) (.num 1)) (.plus (.num 2) (.num 2)))
(.plus (.plus (.num 1) (.num 1)) (.num 4)) :=
.plusRight _ _ _ (.plus 2 2) hd:Deterministic ANSteps₁:((Aexp.num 1).plus (Aexp.num 1)).plus ((Aexp.num 2).plus (Aexp.num 2)) ⟶n
(Aexp.num 2).plus ((Aexp.num 2).plus (Aexp.num 2))s₂:((Aexp.num 1).plus (Aexp.num 1)).plus ((Aexp.num 2).plus (Aexp.num 2)) ⟶n
((Aexp.num 1).plus (Aexp.num 1)).plus (Aexp.num 4)⊢ False
have heq := hd _ _ _ s₁ s₂ hd:Deterministic ANSteps₁:((Aexp.num 1).plus (Aexp.num 1)).plus ((Aexp.num 2).plus (Aexp.num 2)) ⟶n
(Aexp.num 2).plus ((Aexp.num 2).plus (Aexp.num 2))s₂:((Aexp.num 1).plus (Aexp.num 1)).plus ((Aexp.num 2).plus (Aexp.num 2)) ⟶n
((Aexp.num 1).plus (Aexp.num 1)).plus (Aexp.num 4)heq:(Aexp.num 2).plus ((Aexp.num 2).plus (Aexp.num 2)) = ((Aexp.num 1).plus (Aexp.num 1)).plus (Aexp.num 4)⊢ False
simp at heq All goals completed! 🐙
Remarkably, this nondeterminism does not affect the final answer. The key
observation is that a single step never changes the big-step value of an
expression — whichever operand we advance, eval is preserved.
Prove that one nondeterministic step leaves the big-step value unchanged.
Hint: induction on the step derivation; each case is immediate from eval
and, where present, the induction hypothesis.
theorem anstep_preserves_eval (a a' : Aexp) (h : a ⟶n a') : a.eval = a'.eval := by a:Aexpa':Aexph:a ⟶n a'⊢ a.eval = a'.eval
solution!
induction h plusLeft a:Aexpa':Aexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶n a₁'✝h_ih✝:a₁✝.eval = a₁'✝.eval⊢ (a₁✝.plus a₂✝).eval = (a₁'✝.plus a₂✝).evalplusRight a:Aexpa':Aexpa₁✝:Aexpa₂✝:Aexpa₂'✝:Aexph✝:a₂✝ ⟶n a₂'✝h_ih✝:a₂✝.eval = a₂'✝.eval⊢ (a₁✝.plus a₂✝).eval = (a₁✝.plus a₂'✝).evalplus a:Aexpa':Aexpn₁✝:Natn₂✝:Nat⊢ ((Aexp.num n₁✝).plus (Aexp.num n₂✝)).eval = (Aexp.num (n₁✝ + n₂✝)).evalminusLeft a:Aexpa':Aexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶n a₁'✝h_ih✝:a₁✝.eval = a₁'✝.eval⊢ (a₁✝.minus a₂✝).eval = (a₁'✝.minus a₂✝).evalminusRight a:Aexpa':Aexpa₁✝:Aexpa₂✝:Aexpa₂'✝:Aexph✝:a₂✝ ⟶n a₂'✝h_ih✝:a₂✝.eval = a₂'✝.eval⊢ (a₁✝.minus a₂✝).eval = (a₁✝.minus a₂'✝).evalminus a:Aexpa':Aexpn₁✝:Natn₂✝:Nat⊢ ((Aexp.num n₁✝).minus (Aexp.num n₂✝)).eval = (Aexp.num (n₁✝ - n₂✝)).evalmultLeft a:Aexpa':Aexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶n a₁'✝h_ih✝:a₁✝.eval = a₁'✝.eval⊢ (a₁✝.mult a₂✝).eval = (a₁'✝.mult a₂✝).evalmultRight a:Aexpa':Aexpa₁✝:Aexpa₂✝:Aexpa₂'✝:Aexph✝:a₂✝ ⟶n a₂'✝h_ih✝:a₂✝.eval = a₂'✝.eval⊢ (a₁✝.mult a₂✝).eval = (a₁✝.mult a₂'✝).evalmult a:Aexpa':Aexpn₁✝:Natn₂✝:Nat⊢ ((Aexp.num n₁✝).mult (Aexp.num n₂✝)).eval = (Aexp.num (n₁✝ * n₂✝)).eval <;> plusLeft a:Aexpa':Aexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶n a₁'✝h_ih✝:a₁✝.eval = a₁'✝.eval⊢ (a₁✝.plus a₂✝).eval = (a₁'✝.plus a₂✝).evalplusRight a:Aexpa':Aexpa₁✝:Aexpa₂✝:Aexpa₂'✝:Aexph✝:a₂✝ ⟶n a₂'✝h_ih✝:a₂✝.eval = a₂'✝.eval⊢ (a₁✝.plus a₂✝).eval = (a₁✝.plus a₂'✝).evalplus a:Aexpa':Aexpn₁✝:Natn₂✝:Nat⊢ ((Aexp.num n₁✝).plus (Aexp.num n₂✝)).eval = (Aexp.num (n₁✝ + n₂✝)).evalminusLeft a:Aexpa':Aexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶n a₁'✝h_ih✝:a₁✝.eval = a₁'✝.eval⊢ (a₁✝.minus a₂✝).eval = (a₁'✝.minus a₂✝).evalminusRight a:Aexpa':Aexpa₁✝:Aexpa₂✝:Aexpa₂'✝:Aexph✝:a₂✝ ⟶n a₂'✝h_ih✝:a₂✝.eval = a₂'✝.eval⊢ (a₁✝.minus a₂✝).eval = (a₁✝.minus a₂'✝).evalminus a:Aexpa':Aexpn₁✝:Natn₂✝:Nat⊢ ((Aexp.num n₁✝).minus (Aexp.num n₂✝)).eval = (Aexp.num (n₁✝ - n₂✝)).evalmultLeft a:Aexpa':Aexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶n a₁'✝h_ih✝:a₁✝.eval = a₁'✝.eval⊢ (a₁✝.mult a₂✝).eval = (a₁'✝.mult a₂✝).evalmultRight a:Aexpa':Aexpa₁✝:Aexpa₂✝:Aexpa₂'✝:Aexph✝:a₂✝ ⟶n a₂'✝h_ih✝:a₂✝.eval = a₂'✝.eval⊢ (a₁✝.mult a₂✝).eval = (a₁✝.mult a₂'✝).evalmult a:Aexpa':Aexpn₁✝:Natn₂✝:Nat⊢ ((Aexp.num n₁✝).mult (Aexp.num n₂✝)).eval = (Aexp.num (n₁✝ * n₂✝)).eval simp only [Aexp.eval, *] All goals completed! 🐙
This lifts to any number of steps by a routine induction on the multi-step derivation:
theorem multi_anstep_preserves_eval (a a' : Aexp) (h : Multi ANStep a a') : a.eval = a'.eval := by a:Aexpa':Aexph:Multi ANStep a a'⊢ a.eval = a'.eval
induction h with
| refl x => refl a:Aexpa':Aexpx:Aexp⊢ x.eval = x.eval rfl All goals completed! 🐙
| step x y z h₁ _ ih => step a:Aexpa':Aexpx:Aexpy:Aexpz:Aexph₁:x ⟶n yh₂✝:Multi ANStep y zih:y.eval = z.eval⊢ x.eval = z.eval rw [anstep_preserves_eval x y h₁ step a:Aexpa':Aexpx:Aexpy:Aexpz:Aexph₁:x ⟶n yh₂✝:Multi ANStep y zih:y.eval = z.eval⊢ y.eval = z.eval] step a:Aexpa':Aexpx:Aexpy:Aexpz:Aexph₁:x ⟶n yh₂✝:Multi ANStep y zih:y.eval = z.eval⊢ y.eval = z.eval; exact ih All goals completed! 🐙
Finally we can compare the two semantics. The deterministic relation ⟶a is
a special case of ⟶n: every ⟶a step is also an ⟶n step (it merely
happens, in addition, to respect the IsAValue guard).
theorem astep_imp_anstep (a a' : Aexp) (h : a ⟶a a') : a ⟶n a' := by a:Aexpa':Aexph:a ⟶a a'⊢ a ⟶n a'
induction h with
| plusLeft a₁ a₁' a₂ _ ih => plusLeft a:Aexpa':Aexpa₁:Aexpa₁':Aexpa₂:Aexph✝:a₁ ⟶a a₁'ih:a₁ ⟶n a₁'⊢ a₁.plus a₂ ⟶n a₁'.plus a₂ exact .plusLeft a₁ a₁' a₂ ih All goals completed! 🐙
| plusRight v₁ a₂ a₂' _ _ ih => plusRight a:Aexpa':Aexpv₁:Aexpa₂:Aexpa₂':Aexphv✝:IsAValue v₁h✝:a₂ ⟶a a₂'ih:a₂ ⟶n a₂'⊢ v₁.plus a₂ ⟶n v₁.plus a₂' exact .plusRight v₁ a₂ a₂' ih All goals completed! 🐙
| plus n₁ n₂ => plus a:Aexpa':Aexpn₁:Natn₂:Nat⊢ (Aexp.num n₁).plus (Aexp.num n₂) ⟶n Aexp.num (n₁ + n₂) exact .plus n₁ n₂ All goals completed! 🐙
| minusLeft a₁ a₁' a₂ _ ih => minusLeft a:Aexpa':Aexpa₁:Aexpa₁':Aexpa₂:Aexph✝:a₁ ⟶a a₁'ih:a₁ ⟶n a₁'⊢ a₁.minus a₂ ⟶n a₁'.minus a₂ exact .minusLeft a₁ a₁' a₂ ih All goals completed! 🐙
| minusRight v₁ a₂ a₂' _ _ ih => minusRight a:Aexpa':Aexpv₁:Aexpa₂:Aexpa₂':Aexphv✝:IsAValue v₁h✝:a₂ ⟶a a₂'ih:a₂ ⟶n a₂'⊢ v₁.minus a₂ ⟶n v₁.minus a₂' exact .minusRight v₁ a₂ a₂' ih All goals completed! 🐙
| minus n₁ n₂ => minus a:Aexpa':Aexpn₁:Natn₂:Nat⊢ (Aexp.num n₁).minus (Aexp.num n₂) ⟶n Aexp.num (n₁ - n₂) exact .minus n₁ n₂ All goals completed! 🐙
| multLeft a₁ a₁' a₂ _ ih => multLeft a:Aexpa':Aexpa₁:Aexpa₁':Aexpa₂:Aexph✝:a₁ ⟶a a₁'ih:a₁ ⟶n a₁'⊢ a₁.mult a₂ ⟶n a₁'.mult a₂ exact .multLeft a₁ a₁' a₂ ih All goals completed! 🐙
| multRight v₁ a₂ a₂' _ _ ih => multRight a:Aexpa':Aexpv₁:Aexpa₂:Aexpa₂':Aexphv✝:IsAValue v₁h✝:a₂ ⟶a a₂'ih:a₂ ⟶n a₂'⊢ v₁.mult a₂ ⟶n v₁.mult a₂' exact .multRight v₁ a₂ a₂' ih All goals completed! 🐙
| mult n₁ n₂ => mult a:Aexpa':Aexpn₁:Natn₂:Nat⊢ (Aexp.num n₁).mult (Aexp.num n₂) ⟶n Aexp.num (n₁ * n₂) exact .mult n₁ n₂ All goals completed! 🐙
theorem multi_astep_imp_anstep (a a' : Aexp) (h : Multi AStep a a') : Multi ANStep a a' := by a:Aexpa':Aexph:Multi AStep a a'⊢ Multi ANStep a a'
induction h with
| refl x => refl a:Aexpa':Aexpx:Aexp⊢ Multi ANStep x x exact .refl x All goals completed! 🐙
| step x y z h₁ _ ih => step a:Aexpa':Aexpx:Aexpy:Aexpz:Aexph₁:x ⟶a yh₂✝:Multi AStep y zih:Multi ANStep y z⊢ Multi ANStep x z exact .step x y z (astep_imp_anstep x y h₁) ih All goals completed! 🐙
Now put the pieces together: prove that the deterministic and nondeterministic
semantics always compute the same final result. That is, if a fully
reduces to .num n₁ under ⟶a and to .num n₂ under ⟶n, then n₁ = n₂.
Hint: both .num n₁ and .num n₂ are reachable by ⟶n (use
multi_astep_imp_anstep for the first), and ⟶n preserves eval.
theorem astep_anstep_agree (a : Aexp) (n₁ n₂ : Nat)
(hd : Multi AStep a (.num n₁)) (hn : Multi ANStep a (.num n₂)) : n₁ = n₂ := by a:Aexpn₁:Natn₂:Nathd:Multi AStep a (Aexp.num n₁)hn:Multi ANStep a (Aexp.num n₂)⊢ n₁ = n₂
solution!
have e₁ := multi_anstep_preserves_eval a (.num n₁)
(multi_astep_imp_anstep a (.num n₁) hd) a:Aexpn₁:Natn₂:Nathd:Multi AStep a (Aexp.num n₁)hn:Multi ANStep a (Aexp.num n₂)e₁:a.eval = (Aexp.num n₁).eval⊢ n₁ = n₂
have e₂ := multi_anstep_preserves_eval a (.num n₂) hn a:Aexpn₁:Natn₂:Nathd:Multi AStep a (Aexp.num n₁)hn:Multi ANStep a (Aexp.num n₂)e₁:a.eval = (Aexp.num n₁).evale₂:a.eval = (Aexp.num n₂).eval⊢ n₁ = n₂
simp only [Aexp.eval] at e₁ e₂ a:Aexpn₁:Natn₂:Nathd:Multi AStep a (Aexp.num n₁)hn:Multi ANStep a (Aexp.num n₂)e₁:a.eval = n₁e₂:a.eval = n₂⊢ n₁ = n₂
lia All goals completed! 🐙
So even though ⟶n is genuinely nondeterministic, the value it eventually
produces is completely determined — and it is the same value the deterministic
machine (and the big-step evaluator) computes. This confluence to a unique
result is exactly the property one wants when reordering or parallelizing the
evaluation of pure expressions.
3.5.4. A Small-Step Stack Machine
Our last example is a small-step semantics for a stack machine that evaluates
arithmetic expressions. The machine's instructions push a constant or combine the
top two stack entries. The machine's behavior should match the big-step Aexp.eval
function defined earlier.
A program is a list of instructions, and the stack is a list of numbers.
inductive SInstr where
| push (n : Nat)
| plus
| minus
| mult
abbrev Stack := List Nat
abbrev Prog := List SInstr
The compiler emits code in the postfix order sketched above:
def compile : Aexp → Prog
| .num n => [.push n]
| .plus a₁ a₂ => compile a₁ ++ compile a₂ ++ [.plus]
| .minus a₁ a₂ => compile a₁ ++ compile a₂ ++ [.minus]
| .mult a₁ a₂ => compile a₁ ++ compile a₂ ++ [.mult]
example : compile (.plus (.num 2) (.num 3)) = [.push 2, .push 3, .plus] := rfl
Now the small-step machine itself: each step consumes the next instruction and updates the stack.
inductive StackStep : Prog × Stack → Prog × Stack → Prop where
| push (p : Prog) (stk : Stack) (n : Nat) : StackStep (.push n :: p, stk) (p, n :: stk)
| plus (p : Prog) (stk : Stack) (n m : Nat) :
StackStep (.plus :: p, n :: m :: stk) (p, (m + n) :: stk)
| minus (p : Prog) (stk : Stack) (n m : Nat) :
StackStep (.minus :: p, n :: m :: stk) (p, (m - n) :: stk)
| mult (p : Prog) (stk : Stack) (n m : Nat) :
StackStep (.mult :: p, n :: m :: stk) (p, (m * n) :: stk)
The machine is deterministic:
theorem stack_step_deterministic : Deterministic StackStep := by ⊢ Deterministic StackStep
intro x y₁ y₂ h₁ h₂ x:Prog × Stacky₁:Prog × Stacky₂:Prog × Stackh₁:StackStep x y₁h₂:StackStep x y₂⊢ y₁ = y₂
cases h₁ push y₂:Prog × Stackp✝:Progstk✝:Stackn✝:Nath₂:StackStep (SInstr.push n✝ :: p✝, stk✝) y₂⊢ (p✝, n✝ :: stk✝) = y₂plus y₂:Prog × Stackp✝:Progstk✝:Stackn✝:Natm✝:Nath₂:StackStep (SInstr.plus :: p✝, n✝ :: m✝ :: stk✝) y₂⊢ (p✝, (m✝ + n✝) :: stk✝) = y₂minus y₂:Prog × Stackp✝:Progstk✝:Stackn✝:Natm✝:Nath₂:StackStep (SInstr.minus :: p✝, n✝ :: m✝ :: stk✝) y₂⊢ (p✝, (m✝ - n✝) :: stk✝) = y₂mult y₂:Prog × Stackp✝:Progstk✝:Stackn✝:Natm✝:Nath₂:StackStep (SInstr.mult :: p✝, n✝ :: m✝ :: stk✝) y₂⊢ (p✝, m✝ * n✝ :: stk✝) = y₂ <;> push y₂:Prog × Stackp✝:Progstk✝:Stackn✝:Nath₂:StackStep (SInstr.push n✝ :: p✝, stk✝) y₂⊢ (p✝, n✝ :: stk✝) = y₂plus y₂:Prog × Stackp✝:Progstk✝:Stackn✝:Natm✝:Nath₂:StackStep (SInstr.plus :: p✝, n✝ :: m✝ :: stk✝) y₂⊢ (p✝, (m✝ + n✝) :: stk✝) = y₂minus y₂:Prog × Stackp✝:Progstk✝:Stackn✝:Natm✝:Nath₂:StackStep (SInstr.minus :: p✝, n✝ :: m✝ :: stk✝) y₂⊢ (p✝, (m✝ - n✝) :: stk✝) = y₂mult y₂:Prog × Stackp✝:Progstk✝:Stackn✝:Natm✝:Nath₂:StackStep (SInstr.mult :: p✝, n✝ :: m✝ :: stk✝) y₂⊢ (p✝, m✝ * n✝ :: stk✝) = y₂ cases h₂ mult.mult p✝:Progstk✝:Stackn✝:Natm✝:Nat⊢ (p✝, m✝ * n✝ :: stk✝) = (p✝, m✝ * n✝ :: stk✝) <;> push.push p✝:Progstk✝:Stackn✝:Nat⊢ (p✝, n✝ :: stk✝) = (p✝, n✝ :: stk✝)plus.plus p✝:Progstk✝:Stackn✝:Natm✝:Nat⊢ (p✝, (m✝ + n✝) :: stk✝) = (p✝, (m✝ + n✝) :: stk✝)minus.minus p✝:Progstk✝:Stackn✝:Natm✝:Nat⊢ (p✝, (m✝ - n✝) :: stk✝) = (p✝, (m✝ - n✝) :: stk✝)mult.mult p✝:Progstk✝:Stackn✝:Natm✝:Nat⊢ (p✝, m✝ * n✝ :: stk✝) = (p✝, m✝ * n✝ :: stk✝) rfl All goals completed! 🐙
Prove the compiler correct: running the compiled program from the empty stack reduces, in some number of steps, to a stack holding exactly the value of the expression.
Hint: this will not go through by a direct induction — the induction
hypothesis is too weak. Prove a more general statement first, about running
compile a followed by any leftover program p, starting from any stack
stk. (Reassociating the ++s with List.append_assoc, and chaining steps
with multi_trans/multi_single, are the moves you need.)
theorem compiler_is_correct (a : Aexp) :
Multi StackStep (compile a, []) ([], [a.eval]) := by a:Aexp⊢ Multi StackStep (compile a, []) ([], [a.eval])
solution!
have gen : ∀ (a : Aexp) (p : Prog) (stk : Stack),
Multi StackStep (compile a ++ p, stk) (p, a.eval :: stk) := by
intro a a✝:Aexpa:Aexp⊢ ∀ (p : Prog) (stk : Stack), Multi StackStep (compile a ++ p, stk) (p, a.eval :: stk)
induction a with
| num n => num a:Aexpn:Nat⊢ ∀ (p : Prog) (stk : Stack), Multi StackStep (compile (Aexp.num n) ++ p, stk) (p, (Aexp.num n).eval :: stk)
intro p stk num a:Aexpn:Natp:Progstk:Stack⊢ Multi StackStep (compile (Aexp.num n) ++ p, stk) (p, (Aexp.num n).eval :: stk)
simp only [compile, Aexp.eval] num a:Aexpn:Natp:Progstk:Stack⊢ Multi StackStep ([SInstr.push n] ++ p, stk) (p, n :: stk)
exact multi_single _ _ _ (StackStep.push p stk n) All goals completed! 🐙
| plus a₁ a₂ ih₁ ih₂ => plus a:Aexpa₁:Aexpa₂:Aexpih₁:∀ (p : Prog) (stk : Stack), Multi StackStep (compile a₁ ++ p, stk) (p, a₁.eval :: stk)ih₂:∀ (p : Prog) (stk : Stack), Multi StackStep (compile a₂ ++ p, stk) (p, a₂.eval :: stk)⊢ ∀ (p : Prog) (stk : Stack), Multi StackStep (compile (a₁.plus a₂) ++ p, stk) (p, (a₁.plus a₂).eval :: stk)
intro p stk plus a:Aexpa₁:Aexpa₂:Aexpih₁:∀ (p : Prog) (stk : Stack), Multi StackStep (compile a₁ ++ p, stk) (p, a₁.eval :: stk)ih₂:∀ (p : Prog) (stk : Stack), Multi StackStep (compile a₂ ++ p, stk) (p, a₂.eval :: stk)p:Progstk:Stack⊢ Multi StackStep (compile (a₁.plus a₂) ++ p, stk) (p, (a₁.plus a₂).eval :: stk)
simp only [compile, Aexp.eval, List.append_assoc] plus a:Aexpa₁:Aexpa₂:Aexpih₁:∀ (p : Prog) (stk : Stack), Multi StackStep (compile a₁ ++ p, stk) (p, a₁.eval :: stk)ih₂:∀ (p : Prog) (stk : Stack), Multi StackStep (compile a₂ ++ p, stk) (p, a₂.eval :: stk)p:Progstk:Stack⊢ Multi StackStep (compile a₁ ++ (compile a₂ ++ ([SInstr.plus] ++ p)), stk) (p, (a₁.eval + a₂.eval) :: stk)
exact multi_trans _ _ _ _ (ih₁ _ stk)
(multi_trans _ _ _ _ (ih₂ _ (a₁.eval :: stk))
(multi_single _ _ _ (StackStep.plus p stk a₂.eval a₁.eval))) All goals completed! 🐙
| minus a₁ a₂ ih₁ ih₂ => minus a:Aexpa₁:Aexpa₂:Aexpih₁:∀ (p : Prog) (stk : Stack), Multi StackStep (compile a₁ ++ p, stk) (p, a₁.eval :: stk)ih₂:∀ (p : Prog) (stk : Stack), Multi StackStep (compile a₂ ++ p, stk) (p, a₂.eval :: stk)⊢ ∀ (p : Prog) (stk : Stack), Multi StackStep (compile (a₁.minus a₂) ++ p, stk) (p, (a₁.minus a₂).eval :: stk)
intro p stk minus a:Aexpa₁:Aexpa₂:Aexpih₁:∀ (p : Prog) (stk : Stack), Multi StackStep (compile a₁ ++ p, stk) (p, a₁.eval :: stk)ih₂:∀ (p : Prog) (stk : Stack), Multi StackStep (compile a₂ ++ p, stk) (p, a₂.eval :: stk)p:Progstk:Stack⊢ Multi StackStep (compile (a₁.minus a₂) ++ p, stk) (p, (a₁.minus a₂).eval :: stk)
simp only [compile, Aexp.eval, List.append_assoc] minus a:Aexpa₁:Aexpa₂:Aexpih₁:∀ (p : Prog) (stk : Stack), Multi StackStep (compile a₁ ++ p, stk) (p, a₁.eval :: stk)ih₂:∀ (p : Prog) (stk : Stack), Multi StackStep (compile a₂ ++ p, stk) (p, a₂.eval :: stk)p:Progstk:Stack⊢ Multi StackStep (compile a₁ ++ (compile a₂ ++ ([SInstr.minus] ++ p)), stk) (p, (a₁.eval - a₂.eval) :: stk)
exact multi_trans _ _ _ _ (ih₁ _ stk)
(multi_trans _ _ _ _ (ih₂ _ (a₁.eval :: stk))
(multi_single _ _ _ (StackStep.minus p stk a₂.eval a₁.eval))) All goals completed! 🐙
| mult a₁ a₂ ih₁ ih₂ => mult a:Aexpa₁:Aexpa₂:Aexpih₁:∀ (p : Prog) (stk : Stack), Multi StackStep (compile a₁ ++ p, stk) (p, a₁.eval :: stk)ih₂:∀ (p : Prog) (stk : Stack), Multi StackStep (compile a₂ ++ p, stk) (p, a₂.eval :: stk)⊢ ∀ (p : Prog) (stk : Stack), Multi StackStep (compile (a₁.mult a₂) ++ p, stk) (p, (a₁.mult a₂).eval :: stk)
intro p stk mult a:Aexpa₁:Aexpa₂:Aexpih₁:∀ (p : Prog) (stk : Stack), Multi StackStep (compile a₁ ++ p, stk) (p, a₁.eval :: stk)ih₂:∀ (p : Prog) (stk : Stack), Multi StackStep (compile a₂ ++ p, stk) (p, a₂.eval :: stk)p:Progstk:Stack⊢ Multi StackStep (compile (a₁.mult a₂) ++ p, stk) (p, (a₁.mult a₂).eval :: stk)
simp only [compile, Aexp.eval, List.append_assoc] mult a:Aexpa₁:Aexpa₂:Aexpih₁:∀ (p : Prog) (stk : Stack), Multi StackStep (compile a₁ ++ p, stk) (p, a₁.eval :: stk)ih₂:∀ (p : Prog) (stk : Stack), Multi StackStep (compile a₂ ++ p, stk) (p, a₂.eval :: stk)p:Progstk:Stack⊢ Multi StackStep (compile a₁ ++ (compile a₂ ++ ([SInstr.mult] ++ p)), stk) (p, a₁.eval * a₂.eval :: stk)
exact multi_trans _ _ _ _ (ih₁ _ stk)
(multi_trans _ _ _ _ (ih₂ _ (a₁.eval :: stk))
(multi_single _ _ _ (StackStep.mult p stk a₂.eval a₁.eval))) a:Aexpgen:∀ (a : Aexp) (p : Prog) (stk : Stack), Multi StackStep (compile a ++ p, stk) (p, a.eval :: stk)⊢ Multi StackStep (compile a, []) ([], [a.eval])
have hfin := gen a [] [] a:Aexpgen:∀ (a : Aexp) (p : Prog) (stk : Stack), Multi StackStep (compile a ++ p, stk) (p, a.eval :: stk)hfin:Multi StackStep (compile a ++ [], []) ([], [a.eval])⊢ Multi StackStep (compile a, []) ([], [a.eval])
simp only [List.append_nil] at hfin a:Aexpgen:∀ (a : Aexp) (p : Prog) (stk : Stack), Multi StackStep (compile a ++ p, stk) (p, a.eval :: stk)hfin:Multi StackStep (compile a, []) ([], [a.eval])⊢ Multi StackStep (compile a, []) ([], [a.eval])
exact hfin All goals completed! 🐙
end Slang
3.5.5. Automation with solve_by_elim
When experimenting with definitions of programming languages
in Lean, we often want to see what a particular term steps
to - i.e., we want to find proofs for goals of the form t ⟶* t'.
Consider, for example, reducing an arithmetic expression using the small-step
relation AStep.
example : (.p (.c 3) (.p (.c 3) (.c 4))) ⟶* (.c 10) := by ⊢ (Tm.c 3).p ((Tm.c 3).p (Tm.c 4)) ⟶* Tm.c 10
apply Multi.step (y := .p (.c 3) (.c 7)) h₁ ⊢ (Tm.c 3).p ((Tm.c 3).p (Tm.c 4)) ⟶ (Tm.c 3).p (Tm.c 7)h₂ ⊢ (Tm.c 3).p (Tm.c 7) ⟶* Tm.c 10
· h₁ ⊢ (Tm.c 3).p ((Tm.c 3).p (Tm.c 4)) ⟶ (Tm.c 3).p (Tm.c 7) apply Step.plusRight h₁.hv ⊢ IsValue (Tm.c 3)h₁.h ⊢ (Tm.c 3).p (Tm.c 4) ⟶ Tm.c 7
· h₁.hv ⊢ IsValue (Tm.c 3) apply IsValue.const All goals completed! 🐙
· h₁.h ⊢ (Tm.c 3).p (Tm.c 4) ⟶ Tm.c 7 apply Step.plus All goals completed! 🐙
· h₂ ⊢ (Tm.c 3).p (Tm.c 7) ⟶* Tm.c 10 apply Multi.step (y := .c 10) h₂.h₁ ⊢ (Tm.c 3).p (Tm.c 7) ⟶ Tm.c 10h₂.h₂ ⊢ Tm.c 10 ⟶* Tm.c 10
· h₂.h₁ ⊢ (Tm.c 3).p (Tm.c 7) ⟶ Tm.c 10 apply Step.plus All goals completed! 🐙
· h₂.h₂ ⊢ Tm.c 10 ⟶* Tm.c 10 apply Multi.refl All goals completed! 🐙
Proofs that one term normalizes to another must repeatedly apply
Multi.step until the term reaches a normal form, with some very simple
intermediate steps along the way. Thankfully, we can automate this process
with a new tactic: solve_by_elim. When supplied with a list of
constructors, solve_by_elim [c₁, c₂, c₃, ...] will attempt to apply
these constructors repeatedly to a goal. It will also automatically
attempt to use simple tactics like rfl, trivial, congr and hypotheses
from the context in order to solve simple goals. So, for example,
the proof above also be written:
example : (.p (.c 3) (.p (.c 3) (.c 4))) ⟶* (.c 10) := by ⊢ (Tm.c 3).p ((Tm.c 3).p (Tm.c 4)) ⟶* Tm.c 10
repeat apply Multi.step h₂.h₁ ⊢ (Tm.c 3).p (Tm.c (3 + 4)) ⟶ ?h₂.yh₂.h₂ ⊢ ?h₂.y ⟶* Tm.c 10h₂.y ⊢ Tm <;> h₂.h₁ ⊢ (Tm.c 3).p (Tm.c (3 + 4)) ⟶ ?h₂.yh₂.h₂ ⊢ ?h₂.y ⟶* Tm.c 10h₂.y ⊢ Tm
try solve_by_elim [Step.plusRight, Step.plusLeft, Step.plus, IsValue.const] All goals completed! 🐙
This one script would suffice to prove most concrete reduction sequences
for this simple language. To make it work for others, we would need to supply
constructors for those other languages to solve_by_elim. The languages we
will study in this book can grow to a large number of constructors for their Step
relations, so we'd like a way to supply all of them to solve_by_elim more easily.
Luckily, Lean supports this. We can register a constructor (or lemma) for use with
solve_by_elim with an attribute command:
Attributes
The command below tags all of these constructors with the SimpleArith attribute,
which we can then use to automatically pull all of these constructors in when we use
solve_by_elim. However, due to a limitation of Lean, this attribute needs to be
pre-declared in a different file; we can't create it here and then immediately use it.
For this book, we've predeclared all the attributes we'll use in a file called
AttributeDecls.lean, following the typical pattern from libraries like Mathlib.
attribute [SimpleArith] Step.plusRight Step.plusLeft Step.plus IsValue.const
This using option then tells solve_by_elim to try to use every constructor
we've registered with the supplied attribute:
example : (.p (.c 3) (.p (.c 3) (.c 4))) ⟶* (.c 10) := by ⊢ (Tm.c 3).p ((Tm.c 3).p (Tm.c 4)) ⟶* Tm.c 10
repeat apply Multi.step h₂.h₁ ⊢ (Tm.c 3).p (Tm.c (3 + 4)) ⟶ ?h₂.yh₂.h₂ ⊢ ?h₂.y ⟶* Tm.c 10h₂.y ⊢ Tm <;> h₂.h₁ ⊢ (Tm.c 3).p (Tm.c (3 + 4)) ⟶ ?h₂.yh₂.h₂ ⊢ ?h₂.y ⟶* Tm.c 10h₂.y ⊢ Tm
try solve_by_elim using SimpleArith All goals completed! 🐙
We can package all this up into a dedicated tactic for solving reduction sequences,
which we'll call normalize:
syntax "normalize" " using " ident,+ : tactic
macro_rules
| `(tactic| normalize using $xs,*) =>
`(tactic|
first
| apply Multi.refl
| (apply Multi.step
· solve_by_elim (maxDepth := 15) (constructor := false) only using $xs,*
· normalize using $xs,*))
And voilà:
example : (.p (.c 3) (.p (.c 3) (.c 4))) ⟶* (.c 10) := by ⊢ (Tm.c 3).p ((Tm.c 3).p (Tm.c 4)) ⟶* Tm.c 10
normalize using SimpleArith All goals completed! 🐙
Use the normalize tactic to prove the following. You will need to supply the
term e' yourself.
theorem normalize_ex : exists e', (.p (.c 3) (.p (.c 2) (.c 1))) ⟶* e' ∧ IsValue e' := by ⊢ ∃ e', (Tm.c 3).p ((Tm.c 2).p (Tm.c 1)) ⟶* e' ∧ IsValue e'
solution!
exists (.c 6) ⊢ (Tm.c 3).p ((Tm.c 2).p (Tm.c 1)) ⟶* Tm.c 6 ∧ IsValue (Tm.c 6); constructor left ⊢ (Tm.c 3).p ((Tm.c 2).p (Tm.c 1)) ⟶* Tm.c 6right ⊢ IsValue (Tm.c 6)
· left ⊢ (Tm.c 3).p ((Tm.c 2).p (Tm.c 1)) ⟶* Tm.c 6 normalize using SimpleArith All goals completed! 🐙
· right ⊢ IsValue (Tm.c 6) constructor All goals completed! 🐙