Type Systems

3. Smallstep: Small-step Operational Semantics🔗

Note to developers (Benjamin Pierce @bcpierce00)

The hiding lean (above in the source file) should not be needed any more and should be removed from all files everywhere it exists.

Note to developers (Michael Hicks @mwhicks1)

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.

Note to developers (before next release)

In this and later chapters, we are not very consistent about presenting computation rules first and congruence rules after...

Note to developers

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 p node is replaced by its value.

  • Each step finds the leftmost p node that is ready to go (both of its operands are constants) and rewrites it in place. The first rule tells how to rewrite this p node 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! 🐙
Exercise★(test_step_2)

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! 🐙
Quiz

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))

Quiz

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.

Note to developers (Michael Hicks @mwhicks1, before next release)

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 plusLeft or plusRight follow by the induction hypothesis.

  • It cannot happen that one is plus and the other is plusLeft/plusRight, since this would imply that x has the form p t₁ t₂ where both t₁ and t₂ are constants (by plus) and one of t₁ or t₂ has the form p _.

  • Similarly, it cannot happen that one is plusLeft and the other is plusRight, since this would imply that x has the form p t₁ t₂ where t₁ has both the form p t₁₁ t₁₂ and the form c 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! 🐙 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₂ 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₂ 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₂✝)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₂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₂'✝ 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₂✝)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₂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 | 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
Note to developers (Michael Hicks @mwhicks1)

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 t as the starting state of the machine.

  • Repeatedly use the ⟶ relation to find a sequence of machine states, starting with t, 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'
Exercise★★★(redo_determinism)

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 plusLeft or plusRight follow by the induction hypothesis.

  • It cannot happen that one is plus and the other is plusLeft/plusRight, since this would imply that x has the form p t₁ t₂ where both t₁ and t₂ are constants (by plus) and one of t₁ or t₂ has the form p _.

  • Similarly, it cannot happen that one is plusLeft and the other is plusRight, since this would imply that x has the form p t₁ t₂ where t₁ both has the form p t₁₁ t₁₂ and is a value (hence has the form c 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 := ⊢ Deterministic Step solution! x:Tmy₁:Tmy₂:Tmh₁:x ⟶ y₁⊢ x ⟶ y₂ → y₁ = y₂ induction h₁ generalizing y₂ with x:Tmy₁:Tmn₁:Natn₂:Naty₂:Tm⊢ (Tm.c n₁).p (Tm.c n₂) ⟶ y₂ → Tm.c (n₁ + n₂) = y₂ x:Tmy₁:Tmn₁:Natn₂:Naty₂:Tmh₂:(Tm.c n₁).p (Tm.c n₂) ⟶ y₂⊢ Tm.c (n₁ + n₂) = y₂; cases h₂ with x:Tmy₁:Tmn₁:Natn₂:Nat⊢ Tm.c (n₁ + n₂) = Tm.c (n₁ + n₂) All goals completed! 🐙 x:Tmy₁:Tmn₁:Natn₂:Natt₁'✝:Tmhs:Tm.c n₁ ⟶ t₁'✝⊢ Tm.c (n₁ + n₂) = t₁'✝.p (Tm.c n₂) All goals completed! 🐙 x:Tmy₁:Tmn₁:Natn₂:Natt₂'✝:Tmhv✝:IsValue (Tm.c n₁)hs:Tm.c n₂ ⟶ t₂'✝⊢ Tm.c (n₁ + n₂) = (Tm.c n₁).p t₂'✝ All goals completed! 🐙 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₂ 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 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₂✝) All goals completed! 🐙 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! 🐙 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₂'✝ 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₂'✝; All goals completed! 🐙 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₂ 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 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₂✝) All goals completed! 🐙 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₂ 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₂; All goals completed! 🐙 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. Then t is a value.

  • Suppose t = p t₁ t₂, where (by the IH) t₁ either is a value or can step to some t₁', and where t₂ is either a value or can step to some t₂'. We must show p t₁ t₂ is either a value or steps to some t'.

    • If t₁ and t₂ are both values, then t can take a step, by plus.

    • If t₁ is a value and t₂ can take a step, then so can t, by plusRight.

    • If t₁ can take a step, then so can t, by plusLeft.

Or, formally:

theorem strong_progress (t : Tm) : IsValue t ∨ ∃ t', t ⟶ t' := t:Tm⊢ IsValue t ∨ ∃ t', t ⟶ t' induction t with n:Nat⊢ IsValue (Tm.c n) ∨ ∃ t', Tm.c n ⟶ t' n:Nat⊢ IsValue (Tm.c n); All goals completed! 🐙 t₁:Tmt₂:Tmih₁:IsValue t₁ ∨ ∃ t', t₁ ⟶ t'ih₂:IsValue t₂ ∨ ∃ t', t₂ ⟶ t'⊢ IsValue (t₁.p t₂) ∨ ∃ t', t₁.p t₂ ⟶ t' t₁:Tmt₂:Tmih₁:IsValue t₁ ∨ ∃ t', t₁ ⟶ t'ih₂:IsValue t₂ ∨ ∃ t', t₂ ⟶ t'⊢ ∃ t', t₁.p t₂ ⟶ t' cases ih₁ with t₁:Tmt₂:Tmih₂:IsValue t₂ ∨ ∃ t', t₂ ⟶ t'hv₁:IsValue t₁⊢ ∃ t', t₁.p t₂ ⟶ t' cases ih₂ with t₁:Tmt₂:Tmhv₁:IsValue t₁hv₂:IsValue t₂⊢ ∃ t', t₁.p t₂ ⟶ t' cases hv₁ with t₂:Tmhv₂:IsValue t₂n₁:Nat⊢ ∃ t', (Tm.c n₁).p t₂ ⟶ t' cases hv₂ with n₁:Natn₂:Nat⊢ ∃ t', (Tm.c n₁).p (Tm.c n₂) ⟶ t' All goals completed! 🐙 t₁:Tmt₂:Tmhv₁:IsValue t₁h₂:∃ t', t₂ ⟶ t'⊢ ∃ t', t₁.p t₂ ⟶ t' t₁:Tmt₂:Tmhv₁:IsValue t₁t₂':Tmht₂:t₂ ⟶ t₂'⊢ ∃ t', t₁.p t₂ ⟶ t' All goals completed! 🐙 t₁:Tmt₂:Tmih₂:IsValue t₂ ∨ ∃ t', t₂ ⟶ t'h₁:∃ t', t₁ ⟶ t'⊢ ∃ t', t₁.p t₂ ⟶ t' t₁:Tmt₂:Tmih₂:IsValue t₂ ∨ ∃ t', t₂ ⟶ t't₁':Tmht₁:t₁ ⟶ t₁'⊢ ∃ t', t₁.p t₂ ⟶ t' 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 := v:Tmh:IsValue v⊢ IsNormalForm Step v v:Tmh:IsValue vhc:∃ t', v ⟶ t'⊢ False v:Tmh:IsValue vt':Tmht:v ⟶ t'⊢ False cases h with t':Tmn:Natht:Tm.c n ⟶ t'⊢ False All goals completed! 🐙 theorem nf_is_value (t : Tm) (h : IsNormalForm Step t) : IsValue t := t:Tmh:IsNormalForm Step t⊢ IsValue t cases strong_progress t with t:Tmh:IsNormalForm Step thv:IsValue t⊢ IsValue t All goals completed! 🐙 t:Tmh:IsNormalForm Step thstep:∃ t', t ⟶ t'⊢ IsValue t All goals completed! 🐙 theorem nf_same_as_value (t : Tm) : IsNormalForm Step t ↔ IsValue t := ⟨nf_is_value t, value_is_nf t⟩
Note to developers (Kihong Heo @KihongHeo)

Tactic absurd is first introduced here. Do we want to explain it?

Note to developers (Daniel Sainati @dsainati1)

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₂')
Quiz

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.
Quiz

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`.
Exercise★★★(value_not_same_as_normal_form1) (Optional)
theorem value_not_same_as_normal_form : ∃ v, IsValue v ∧ ¬ IsNormalForm Step v := ⊢ ∃ v, IsValue v ∧ ¬IsNormalForm Step v ⊢ IsValue ((Tm.c 0).p (Tm.c 0)) ∧ ¬IsNormalForm Step ((Tm.c 0).p (Tm.c 0)) ⊢ ¬IsNormalForm Step ((Tm.c 0).p (Tm.c 0)) solution! h:IsNormalForm Step ((Tm.c 0).p (Tm.c 0))⊢ False All goals completed! 🐙
end Temp1
Exercise★★(value_not_same_as_normal_form2) (Optional)

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₂')
Quiz

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 := ⊢ ∃ v, IsValue v ∧ ¬IsNormalForm Step v ⊢ IsValue (Tm.c 5) ∧ ¬IsNormalForm Step (Tm.c 5) ⊢ ¬IsNormalForm Step (Tm.c 5) solution! h:IsNormalForm Step (Tm.c 5)⊢ False All goals completed! 🐙 end Temp2
Exercise★★★(value_not_same_as_normal_form3) (Optional)

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₂)
Quiz

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 := ⊢ ∃ t, ¬IsValue t ∧ IsNormalForm Step t ⊢ ¬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))) ⊢ ¬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))) ⊢ ¬IsValue ((Tm.c 1).p ((Tm.c 1).p (Tm.c 2))) solution! h:IsValue ((Tm.c 1).p ((Tm.c 1).p (Tm.c 2)))⊢ False; All goals completed! 🐙 ⊢ IsNormalForm Step ((Tm.c 1).p ((Tm.c 1).p (Tm.c 2))) solution! h:∃ t', Step ((Tm.c 1).p ((Tm.c 1).p (Tm.c 2))) t'⊢ False t':Tmht:Step ((Tm.c 1).p ((Tm.c 1).p (Tm.c 2))) t'⊢ False cases ht with t₁'✝:Tmhs:Step (Tm.c 1) t₁'✝⊢ False 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 terms t and t' if t can reach t' by any number (including zero) of single reduction steps.

  • Then we define a "result" of a term t as a normal form that t can 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
Note to developers (berberman)

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 that

    R 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 := ⊢ Tm.c 5 ⟶* Tm.c 5 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) := ⊢ (Tm.c 1).p (Tm.c 2) ⟶* Tm.c (1 + 2) ⊢ (Tm.c 1).p (Tm.c 2) ⟶ Tm.c (1 + 2)⊢ Tm.c (1 + 2) ⟶* Tm.c (1 + 2) ⊢ (Tm.c 1).p (Tm.c 2) ⟶ Tm.c (1 + 2) All goals completed! 🐙 ⊢ Tm.c (1 + 2) ⟶* Tm.c (1 + 2) 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 := X:TypeR:Relation Xx:Xy:Xz:Xg:Multi R x yh:Multi R y z⊢ Multi R x z induction g with X:TypeR:Relation Xx:Xy:Xz:Xa:Xh:Multi R a z⊢ Multi R a z All goals completed! 🐙 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 All goals completed! 🐙

In particular, for the Multi Step relation on terms, if t₁ ⟶* t₂ and t₂ ⟶* t₃, then t₁ ⟶* t₃.

Quiz

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)) := ⊢ ((Tm.c 0).p (Tm.c 3)).p ((Tm.c 2).p (Tm.c 4)) ⟶* Tm.c (0 + 3 + (2 + 4)) ⊢ ((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))⊢ (Tm.c (0 + 3)).p ((Tm.c 2).p (Tm.c 4)) ⟶* Tm.c (0 + 3 + (2 + 4)) ⊢ ((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)) All goals completed! 🐙 ⊢ (Tm.c (0 + 3)).p ((Tm.c 2).p (Tm.c 4)) ⟶ (Tm.c (0 + 3)).p (Tm.c (2 + 4))⊢ (Tm.c (0 + 3)).p (Tm.c (2 + 4)) ⟶* Tm.c (0 + 3 + (2 + 4)) ⊢ (Tm.c (0 + 3)).p ((Tm.c 2).p (Tm.c 4)) ⟶ (Tm.c (0 + 3)).p (Tm.c (2 + 4)) All goals completed! 🐙 ⊢ (Tm.c (0 + 3)).p (Tm.c (2 + 4)) ⟶* Tm.c (0 + 3 + (2 + 4)) All goals completed! 🐙
Exercise★(test_multistep_2) (Optional)
example : (.c 3 : Tm) ⟶* .c 3 := solution!(.refl _)
Exercise★(test_multistep_3) (Optional)
example : (.p (.c 0) (.c 3)) ⟶* .p (.c 0) (.c 3) := solution!(.refl _)
Exercise★★(test_multistep_4)
example : (.p (.c 0) (.p (.c 2) (.p (.c 0) (.c 3)))) ⟶* (.p (.c 0) (.c (2 + (0 + 3)))) := ⊢ (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! ⊢ (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)))⊢ (Tm.c 0).p ((Tm.c 2).p (Tm.c (0 + 3))) ⟶* (Tm.c 0).p (Tm.c (2 + (0 + 3))) ⊢ (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))) All goals completed! 🐙 ⊢ (Tm.c 0).p ((Tm.c 2).p (Tm.c (0 + 3))) ⟶* (Tm.c 0).p (Tm.c (2 + (0 + 3))) All goals completed! 🐙
Exercise★★(test_multistep_rfl)

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) := ⊢ ((Tm.c 1).p (Tm.c 2)).p (Tm.c 4) ⟶* Tm.c (1 + 2 + 4) solution! ⊢ ((Tm.c 1).p (Tm.c 2)).p (Tm.c 4) ⟶ (Tm.c (1 + 2)).p (Tm.c 4)⊢ (Tm.c (1 + 2)).p (Tm.c 4) ⟶* Tm.c (1 + 2 + 4) ⊢ ((Tm.c 1).p (Tm.c 2)).p (Tm.c 4) ⟶ (Tm.c (1 + 2)).p (Tm.c 4) All goals completed! 🐙 ⊢ (Tm.c (1 + 2)).p (Tm.c 4) ⟶ Tm.c (1 + 2 + 4)⊢ Tm.c (1 + 2 + 4) ⟶* Tm.c (1 + 2 + 4) ⊢ (Tm.c (1 + 2)).p (Tm.c 4) ⟶ Tm.c (1 + 2 + 4) All goals completed! 🐙 ⊢ Tm.c (1 + 2 + 4) ⟶* Tm.c (1 + 2 + 4) 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."

Exercise★★★(normal_forms_unique) (Optional)
theorem normal_forms_unique : Deterministic (IsNormalFormOf Step) := ⊢ Deterministic (IsNormalFormOf Step) -- We recommend using this initial setup as-is! x:Tmy₁:Tmy₂:Tmp₁:IsNormalFormOf Step x y₁p₂:IsNormalFormOf Step x y₂⊢ y₁ = y₂ x:Tmy₁:Tmy₂:Tmp₂:IsNormalFormOf Step x y₂p₁₁:x ⟶* y₁p₁₂:IsNormalForm Step y₁⊢ y₁ = y₂ 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 x:Tmy₁:Tma:Tmy₂:Tmp₁₂:IsNormalForm Step ap₂₁:a ⟶* y₂p₂₂:IsNormalForm Step y₂⊢ a = y₂ cases p₂₁ with x:Tmy₁:Tma:Tmp₁₂:IsNormalForm Step ap₂₂:IsNormalForm Step a⊢ a = a All goals completed! 🐙 x:Tmy₁:Tma:Tmy₂:Tmp₁₂:IsNormalForm Step ap₂₂:IsNormalForm Step y₂b:Tmh₁:a ⟶ bh₂✝:b ⟶* y₂⊢ a = y₂ All goals completed! 🐙 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 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 All goals completed! 🐙 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₂ 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₂ 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₂ 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₂) := t₁:Tmt₁':Tmt₂:Tmh:t₁ ⟶* t₁'⊢ t₁.p t₂ ⟶* t₁'.p t₂ induction h with t₁:Tmt₁':Tmt₂:Tmx:Tm⊢ x.p t₂ ⟶* x.p t₂ All goals completed! 🐙 t₁:Tmt₁':Tmt₂:Tmx:Tmy:Tmz:Tmh₁:x ⟶ yh₂:y ⟶* zih:y.p t₂ ⟶* z.p t₂⊢ x.p t₂ ⟶* z.p t₂ All goals completed! 🐙
Exercise★★(multistep_congr_2)
theorem multistep_congr_2 (v₁ t₂ t₂' : Tm) (hv : IsValue v₁) (h : t₂ ⟶* t₂') : (.p v₁ t₂) ⟶* (.p v₁ t₂') := v₁:Tmt₂:Tmt₂':Tmhv:IsValue v₁h:t₂ ⟶* t₂'⊢ v₁.p t₂ ⟶* v₁.p t₂' solution! induction h with v₁:Tmt₂:Tmt₂':Tmhv:IsValue v₁x:Tm⊢ v₁.p x ⟶* v₁.p x All goals completed! 🐙 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 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 n for some n. Here t doesn't take a step, and we have t' = t. We derive the left-hand side by reflexivity and the right-hand side by observing (a) that values are normal forms (by nf_same_as_value) and (b) that t is a value (by const).

  • t = p t₁ t₂ for some t₁ and t₂. By the IH, t₁ and t₂ reduce to normal forms t₁' and t₂'. Recall that normal forms are values (by nf_same_as_value); we therefore know that t₁' = c n₁ and t₂' = c n₂ for some n₁ and n₂. We combine the ⟶* derivations for t₁ and t₂ using multistep_congr_1 and multistep_congr_2 to prove that p t₁ t₂ reduces in many steps to t' = c (n₁ + n₂). Finally, c (n₁ + n₂) is a value, which is in turn a normal form.

theorem step_normalizing : Normalizing Step := ⊢ Normalizing Step t:Tm⊢ ∃ t', IsNormalFormOf Step t t' induction t with n:Nat⊢ ∃ t', IsNormalFormOf Step (Tm.c n) t' All goals completed! 🐙 t₁:Tmt₂:Tmih₁:∃ t', IsNormalFormOf Step t₁ t'ih₂:∃ t', IsNormalFormOf Step t₂ t'⊢ ∃ t', IsNormalFormOf Step (t₁.p t₂) t' t₁:Tmt₂:Tmih₂:∃ t', IsNormalFormOf Step t₂ t't₁':Tmhs₁:t₁ ⟶* t₁'hnf₁:IsNormalForm Step t₁'⊢ ∃ t', IsNormalFormOf Step (t₁.p t₂) t' 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' 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' 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' 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₂)) 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₂) 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₂) 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₂) 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.

Exercise★★★(multistep_of_eval)
theorem multistep_of_eval (t : Tm) (n : Nat) (h : t ⇓ n) : t ⟶* .c n := t:Tmn:Nath:t ⇓ n⊢ t ⟶* Tm.c n solution! induction h with t:Tmn✝:Natn:Nat⊢ Tm.c n ⟶* Tm.c n All goals completed! 🐙 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₂) 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₂) 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₂) 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 plusLeft some number of times to reduce t₁ to a normal form, which must (by nf_same_as_value) be a term of the form c n₁ for some n₁.

  • Next, we use plusRight some number of times to reduce t₂ to a normal form, which must again be a term of the form c n₂ for some n₂.

  • Finally, we use plus one time to reduce p (c n₁) (c n₂) to c (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 ⟶).

Exercise★★★(multistep_of_eval_inf) (Optional, Manually graded)

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.

Exercise★★★(eval_of_step)
theorem eval_of_step (t t' : Tm) (n : Nat) (hs : t ⟶ t') (he : t' ⇓ n) : t ⇓ n := t:Tmt':Tmn:Naths:t ⟶ t'he:t' ⇓ n⊢ t ⇓ n solution! induction hs generalizing n with t:Tmt':Tmn₁:Natn₂:Natn:Nathe:Tm.c (n₁ + n₂) ⇓ n⊢ (Tm.c n₁).p (Tm.c n₂) ⇓ n cases he with t:Tmt':Tmn₁:Natn₂:Nat⊢ (Tm.c n₁).p (Tm.c n₂) ⇓ n₁ + n₂ All goals completed! 🐙 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 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₂ All goals completed! 🐙 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 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₂ 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.)

Exercise★★★(eval_of_multistep)
theorem eval_of_multistep (t t' : Tm) (h : IsNormalFormOf Step t t') : ∃ n, t' = .c n ∧ t ⇓ n := t:Tmt':Tmh:IsNormalFormOf Step t t'⊢ ∃ n, t' = Tm.c n ∧ t ⇓ n solution! t:Tmt':Tmhs:t ⟶* t'hnf:IsNormalForm Step t'⊢ ∃ n, t' = Tm.c n ∧ t ⇓ n t:Tmn:Naths:t ⟶* Tm.c nhnf:IsNormalForm Step (Tm.c n)⊢ ∃ n_1, Tm.c n = Tm.c n_1 ∧ t ⇓ n_1 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 All goals completed! 🐙
Exercise★★★(interp_tm) (Optional)

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 := t:Tmn:Nat⊢ evalF t = n ↔ t ⇓ n solution! t:Tmn:Nat⊢ evalF t = n → t ⇓ nt:Tmn:Nat⊢ (t ⇓ n) → evalF t = n t:Tmn:Nat⊢ evalF t = n → t ⇓ n t:Tmn:Nathi:evalF t = n⊢ t ⇓ n t:Tm⊢ t ⇓ evalF t induction t with n:Nat⊢ Tm.c n ⇓ evalF (Tm.c n) All goals completed! 🐙 t₁:Tmt₂:Tmih₁:t₁ ⇓ evalF t₁ih₂:t₂ ⇓ evalF t₂⊢ t₁.p t₂ ⇓ evalF (t₁.p t₂) All goals completed! 🐙 t:Tmn:Nat⊢ (t ⇓ n) → evalF t = n t:Tmn:Nathe:t ⇓ n⊢ evalF t = n induction he with t:Tmn✝:Natn:Nat⊢ evalF (Tm.c n) = n All goals completed! 🐙 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₂ 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₂; 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)
Exercise★★(strong_progress_arith)

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' := a:Aexp⊢ IsAValue a ∨ ∃ a', a ⟶a a' solution! induction a with n:Nat⊢ IsAValue (Aexp.num n) ∨ ∃ a', Aexp.num n ⟶a a' All goals completed! 🐙 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' 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 a₁:Aexpa₂:Aexpih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'h₁:∃ a', a₁ ⟶a a'⊢ ∃ a', a₁.plus a₂ ⟶a a' a₁:Aexpa₂:Aexpih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'a₁':Aexpha₁:a₁ ⟶a a₁'⊢ ∃ a', a₁.plus a₂ ⟶a a'; All goals completed! 🐙 a₁:Aexpa₂:Aexpih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'hv₁:IsAValue a₁⊢ ∃ a', a₁.plus a₂ ⟶a a' cases hv₁ with a₂:Aexpih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'n₁:Nat⊢ ∃ a', (Aexp.num n₁).plus a₂ ⟶a a' cases ih₂ with a₂:Aexpn₁:Nath₂:∃ a', a₂ ⟶a a'⊢ ∃ a', (Aexp.num n₁).plus a₂ ⟶a a' a₂:Aexpn₁:Nata₂':Aexpha₂:a₂ ⟶a a₂'⊢ ∃ a', (Aexp.num n₁).plus a₂ ⟶a a' All goals completed! 🐙 a₂:Aexpn₁:Nathv₂:IsAValue a₂⊢ ∃ a', (Aexp.num n₁).plus a₂ ⟶a a' cases hv₂ with n₁:Natn₂:Nat⊢ ∃ a', (Aexp.num n₁).plus (Aexp.num n₂) ⟶a a' All goals completed! 🐙 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' 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 a₁:Aexpa₂:Aexpih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'h₁:∃ a', a₁ ⟶a a'⊢ ∃ a', a₁.minus a₂ ⟶a a' a₁:Aexpa₂:Aexpih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'a₁':Aexpha₁:a₁ ⟶a a₁'⊢ ∃ a', a₁.minus a₂ ⟶a a'; All goals completed! 🐙 a₁:Aexpa₂:Aexpih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'hv₁:IsAValue a₁⊢ ∃ a', a₁.minus a₂ ⟶a a' cases hv₁ with a₂:Aexpih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'n₁:Nat⊢ ∃ a', (Aexp.num n₁).minus a₂ ⟶a a' cases ih₂ with a₂:Aexpn₁:Nath₂:∃ a', a₂ ⟶a a'⊢ ∃ a', (Aexp.num n₁).minus a₂ ⟶a a' a₂:Aexpn₁:Nata₂':Aexpha₂:a₂ ⟶a a₂'⊢ ∃ a', (Aexp.num n₁).minus a₂ ⟶a a' All goals completed! 🐙 a₂:Aexpn₁:Nathv₂:IsAValue a₂⊢ ∃ a', (Aexp.num n₁).minus a₂ ⟶a a' cases hv₂ with n₁:Natn₂:Nat⊢ ∃ a', (Aexp.num n₁).minus (Aexp.num n₂) ⟶a a' All goals completed! 🐙 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' 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 a₁:Aexpa₂:Aexpih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'h₁:∃ a', a₁ ⟶a a'⊢ ∃ a', a₁.mult a₂ ⟶a a' a₁:Aexpa₂:Aexpih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'a₁':Aexpha₁:a₁ ⟶a a₁'⊢ ∃ a', a₁.mult a₂ ⟶a a'; All goals completed! 🐙 a₁:Aexpa₂:Aexpih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'hv₁:IsAValue a₁⊢ ∃ a', a₁.mult a₂ ⟶a a' cases hv₁ with a₂:Aexpih₂:IsAValue a₂ ∨ ∃ a', a₂ ⟶a a'n₁:Nat⊢ ∃ a', (Aexp.num n₁).mult a₂ ⟶a a' cases ih₂ with a₂:Aexpn₁:Nath₂:∃ a', a₂ ⟶a a'⊢ ∃ a', (Aexp.num n₁).mult a₂ ⟶a a' a₂:Aexpn₁:Nata₂':Aexpha₂:a₂ ⟶a a₂'⊢ ∃ a', (Aexp.num n₁).mult a₂ ⟶a a' All goals completed! 🐙 a₂:Aexpn₁:Nathv₂:IsAValue a₂⊢ ∃ a', (Aexp.num n₁).mult a₂ ⟶a a' cases hv₂ with n₁:Natn₂:Nat⊢ ∃ a', (Aexp.num n₁).mult (Aexp.num n₂) ⟶a a' 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)
Quiz

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.

Exercise★★★(astep_deterministic)

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 := ⊢ Deterministic AStep solution! x:Aexpy₁:Aexpy₂:Aexph₁:x ⟶a y₁⊢ x ⟶a y₂ → y₁ = y₂ 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₂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₂x:Aexpy₁:Aexpn₁✝:Natn₂✝:Naty₂:Aexp⊢ (Aexp.num n₁✝).plus (Aexp.num n₂✝) ⟶a y₂ → Aexp.num (n₁✝ + n₂✝) = y₂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₂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₂x:Aexpy₁:Aexpn₁✝:Natn₂✝:Naty₂:Aexp⊢ (Aexp.num n₁✝).minus (Aexp.num n₂✝) ⟶a y₂ → Aexp.num (n₁✝ - n₂✝) = y₂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₂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₂x:Aexpy₁:Aexpn₁✝:Natn₂✝:Naty₂:Aexp⊢ (Aexp.num n₁✝).mult (Aexp.num n₂✝) ⟶a y₂ → Aexp.num (n₁✝ * n₂✝) = y₂ 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₂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₂x:Aexpy₁:Aexpn₁✝:Natn₂✝:Naty₂:Aexp⊢ (Aexp.num n₁✝).plus (Aexp.num n₂✝) ⟶a y₂ → Aexp.num (n₁✝ + n₂✝) = y₂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₂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₂x:Aexpy₁:Aexpn₁✝:Natn₂✝:Naty₂:Aexp⊢ (Aexp.num n₁✝).minus (Aexp.num n₂✝) ⟶a y₂ → Aexp.num (n₁✝ - n₂✝) = y₂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₂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₂x:Aexpy₁:Aexpn₁✝:Natn₂✝:Naty₂:Aexp⊢ (Aexp.num n₁✝).mult (Aexp.num n₂✝) ⟶a y₂ → Aexp.num (n₁✝ * n₂✝) = y₂ x:Aexpy₁:Aexpn₁✝:Natn₂✝:Naty₂:Aexph₂:(Aexp.num n₁✝).mult (Aexp.num n₂✝) ⟶a y₂⊢ Aexp.num (n₁✝ * n₂✝) = y₂ 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₂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₂x:Aexpy₁:Aexpn₁✝:Natn₂✝:Naty₂:Aexph₂:(Aexp.num n₁✝).plus (Aexp.num n₂✝) ⟶a y₂⊢ Aexp.num (n₁✝ + n₂✝) = y₂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₂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₂x:Aexpy₁:Aexpn₁✝:Natn₂✝:Naty₂:Aexph₂:(Aexp.num n₁✝).minus (Aexp.num n₂✝) ⟶a y₂⊢ Aexp.num (n₁✝ - n₂✝) = y₂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₂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₂x:Aexpy₁:Aexpn₁✝:Natn₂✝:Naty₂:Aexph₂:(Aexp.num n₁✝).mult (Aexp.num n₂✝) ⟶a y₂⊢ Aexp.num (n₁✝ * n₂✝) = y₂ x:Aexpy₁:Aexpn₁✝:Natn₂✝:Nata₁'✝:Aexph✝:Aexp.num n₁✝ ⟶a a₁'✝⊢ Aexp.num (n₁✝ * n₂✝) = a₁'✝.mult (Aexp.num n₂✝)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₂'✝x:Aexpy₁:Aexpn₁✝:Natn₂✝:Nat⊢ Aexp.num (n₁✝ * n₂✝) = Aexp.num (n₁✝ * n₂✝) 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₂✝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₂'✝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₂✝)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₂✝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₂'✝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₂✝)x:Aexpy₁:Aexpn₁✝:Natn₂✝:Nata₁'✝:Aexph✝:Aexp.num n₁✝ ⟶a a₁'✝⊢ Aexp.num (n₁✝ + n₂✝) = a₁'✝.plus (Aexp.num n₂✝)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₂'✝x:Aexpy₁:Aexpn₁✝:Natn₂✝:Nat⊢ Aexp.num (n₁✝ + n₂✝) = Aexp.num (n₁✝ + n₂✝)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₂✝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₂'✝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₂✝)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₂✝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₂'✝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₂✝)x:Aexpy₁:Aexpn₁✝:Natn₂✝:Nata₁'✝:Aexph✝:Aexp.num n₁✝ ⟶a a₁'✝⊢ Aexp.num (n₁✝ - n₂✝) = a₁'✝.minus (Aexp.num n₂✝)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₂'✝x:Aexpy₁:Aexpn₁✝:Natn₂✝:Nat⊢ Aexp.num (n₁✝ - n₂✝) = Aexp.num (n₁✝ - n₂✝)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₂✝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₂'✝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₂✝)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₂✝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₂'✝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₂✝)x:Aexpy₁:Aexpn₁✝:Natn₂✝:Nata₁'✝:Aexph✝:Aexp.num n₁✝ ⟶a a₁'✝⊢ Aexp.num (n₁✝ * n₂✝) = a₁'✝.mult (Aexp.num n₂✝)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₂'✝x:Aexpy₁:Aexpn₁✝:Natn₂✝:Nat⊢ Aexp.num (n₁✝ * n₂✝) = Aexp.num (n₁✝ * n₂✝) first | All goals completed! 🐙 | All goals completed! 🐙 | (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₂'✝; 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₂'✝) | (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₂'✝ 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 | 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₂'✝ | (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₂'✝ 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₂'✝ All goals completed! 🐙))
Exercise★★★(bstep_deterministic)

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 := ⊢ Deterministic BStep solution! x:Bexpy₁:Bexpy₂:Bexph₁:x ⟶b y₁⊢ x ⟶b y₂ → y₁ = y₂ x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝y₂:Bexp⊢ Bexp.eq a₁✝ a₂✝ ⟶b y₂ → Bexp.eq a₁'✝ a₂✝ = y₂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₂x:Bexpy₁:Bexpn₁✝:Natn₂✝:Naty₂:Bexp⊢ Bexp.eq (Aexp.num n₁✝) (Aexp.num n₂✝) ⟶b y₂ → Bexp.bool (decide (n₁✝ = n₂✝)) = y₂x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝y₂:Bexp⊢ Bexp.neq a₁✝ a₂✝ ⟶b y₂ → Bexp.neq a₁'✝ a₂✝ = y₂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₂x:Bexpy₁:Bexpn₁✝:Natn₂✝:Naty₂:Bexp⊢ Bexp.neq (Aexp.num n₁✝) (Aexp.num n₂✝) ⟶b y₂ → Bexp.bool (decide (n₁✝ ≠ n₂✝)) = y₂x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝y₂:Bexp⊢ Bexp.le a₁✝ a₂✝ ⟶b y₂ → Bexp.le a₁'✝ a₂✝ = y₂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₂x:Bexpy₁:Bexpn₁✝:Natn₂✝:Naty₂:Bexp⊢ Bexp.le (Aexp.num n₁✝) (Aexp.num n₂✝) ⟶b y₂ → Bexp.bool (decide (n₁✝ ≤ n₂✝)) = y₂x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝y₂:Bexp⊢ Bexp.gt a₁✝ a₂✝ ⟶b y₂ → Bexp.gt a₁'✝ a₂✝ = y₂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₂x:Bexpy₁:Bexpn₁✝:Natn₂✝:Naty₂:Bexp⊢ Bexp.gt (Aexp.num n₁✝) (Aexp.num n₂✝) ⟶b y₂ → Bexp.bool (decide (n₁✝ > n₂✝)) = y₂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₂x:Bexpy₁:Bexpy₂:Bexp⊢ (Bexp.bool true).not ⟶b y₂ → Bexp.bool false = y₂x:Bexpy₁:Bexpy₂:Bexp⊢ (Bexp.bool false).not ⟶b y₂ → Bexp.bool true = y₂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₂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₂x:Bexpy₁:Bexpb₂✝:Bexpy₂:Bexp⊢ (Bexp.bool false).and b₂✝ ⟶b y₂ → Bexp.bool false = y₂x:Bexpy₁:Bexpy₂:Bexp⊢ (Bexp.bool true).and (Bexp.bool true) ⟶b y₂ → Bexp.bool true = y₂x:Bexpy₁:Bexpy₂:Bexp⊢ (Bexp.bool true).and (Bexp.bool false) ⟶b y₂ → Bexp.bool false = y₂ x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝y₂:Bexp⊢ Bexp.eq a₁✝ a₂✝ ⟶b y₂ → Bexp.eq a₁'✝ a₂✝ = y₂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₂x:Bexpy₁:Bexpn₁✝:Natn₂✝:Naty₂:Bexp⊢ Bexp.eq (Aexp.num n₁✝) (Aexp.num n₂✝) ⟶b y₂ → Bexp.bool (decide (n₁✝ = n₂✝)) = y₂x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝y₂:Bexp⊢ Bexp.neq a₁✝ a₂✝ ⟶b y₂ → Bexp.neq a₁'✝ a₂✝ = y₂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₂x:Bexpy₁:Bexpn₁✝:Natn₂✝:Naty₂:Bexp⊢ Bexp.neq (Aexp.num n₁✝) (Aexp.num n₂✝) ⟶b y₂ → Bexp.bool (decide (n₁✝ ≠ n₂✝)) = y₂x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝y₂:Bexp⊢ Bexp.le a₁✝ a₂✝ ⟶b y₂ → Bexp.le a₁'✝ a₂✝ = y₂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₂x:Bexpy₁:Bexpn₁✝:Natn₂✝:Naty₂:Bexp⊢ Bexp.le (Aexp.num n₁✝) (Aexp.num n₂✝) ⟶b y₂ → Bexp.bool (decide (n₁✝ ≤ n₂✝)) = y₂x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝y₂:Bexp⊢ Bexp.gt a₁✝ a₂✝ ⟶b y₂ → Bexp.gt a₁'✝ a₂✝ = y₂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₂x:Bexpy₁:Bexpn₁✝:Natn₂✝:Naty₂:Bexp⊢ Bexp.gt (Aexp.num n₁✝) (Aexp.num n₂✝) ⟶b y₂ → Bexp.bool (decide (n₁✝ > n₂✝)) = y₂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₂x:Bexpy₁:Bexpy₂:Bexp⊢ (Bexp.bool true).not ⟶b y₂ → Bexp.bool false = y₂x:Bexpy₁:Bexpy₂:Bexp⊢ (Bexp.bool false).not ⟶b y₂ → Bexp.bool true = y₂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₂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₂x:Bexpy₁:Bexpb₂✝:Bexpy₂:Bexp⊢ (Bexp.bool false).and b₂✝ ⟶b y₂ → Bexp.bool false = y₂x:Bexpy₁:Bexpy₂:Bexp⊢ (Bexp.bool true).and (Bexp.bool true) ⟶b y₂ → Bexp.bool true = y₂x:Bexpy₁:Bexpy₂:Bexp⊢ (Bexp.bool true).and (Bexp.bool false) ⟶b y₂ → Bexp.bool false = y₂ x:Bexpy₁:Bexpy₂:Bexph₂:(Bexp.bool true).and (Bexp.bool false) ⟶b y₂⊢ Bexp.bool false = y₂ x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝y₂:Bexph₂:Bexp.eq a₁✝ a₂✝ ⟶b y₂⊢ Bexp.eq a₁'✝ a₂✝ = y₂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₂x:Bexpy₁:Bexpn₁✝:Natn₂✝:Naty₂:Bexph₂:Bexp.eq (Aexp.num n₁✝) (Aexp.num n₂✝) ⟶b y₂⊢ Bexp.bool (decide (n₁✝ = n₂✝)) = y₂x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝y₂:Bexph₂:Bexp.neq a₁✝ a₂✝ ⟶b y₂⊢ Bexp.neq a₁'✝ a₂✝ = y₂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₂x:Bexpy₁:Bexpn₁✝:Natn₂✝:Naty₂:Bexph₂:Bexp.neq (Aexp.num n₁✝) (Aexp.num n₂✝) ⟶b y₂⊢ Bexp.bool (decide (n₁✝ ≠ n₂✝)) = y₂x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝y₂:Bexph₂:Bexp.le a₁✝ a₂✝ ⟶b y₂⊢ Bexp.le a₁'✝ a₂✝ = y₂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₂x:Bexpy₁:Bexpn₁✝:Natn₂✝:Naty₂:Bexph₂:Bexp.le (Aexp.num n₁✝) (Aexp.num n₂✝) ⟶b y₂⊢ Bexp.bool (decide (n₁✝ ≤ n₂✝)) = y₂x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶a a₁'✝y₂:Bexph₂:Bexp.gt a₁✝ a₂✝ ⟶b y₂⊢ Bexp.gt a₁'✝ a₂✝ = y₂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₂x:Bexpy₁:Bexpn₁✝:Natn₂✝:Naty₂:Bexph₂:Bexp.gt (Aexp.num n₁✝) (Aexp.num n₂✝) ⟶b y₂⊢ Bexp.bool (decide (n₁✝ > n₂✝)) = y₂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₂x:Bexpy₁:Bexpy₂:Bexph₂:(Bexp.bool true).not ⟶b y₂⊢ Bexp.bool false = y₂x:Bexpy₁:Bexpy₂:Bexph₂:(Bexp.bool false).not ⟶b y₂⊢ Bexp.bool true = y₂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₂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₂x:Bexpy₁:Bexpb₂✝:Bexpy₂:Bexph₂:(Bexp.bool false).and b₂✝ ⟶b y₂⊢ Bexp.bool false = y₂x:Bexpy₁:Bexpy₂:Bexph₂:(Bexp.bool true).and (Bexp.bool true) ⟶b y₂⊢ Bexp.bool true = y₂x:Bexpy₁:Bexpy₂:Bexph₂:(Bexp.bool true).and (Bexp.bool false) ⟶b y₂⊢ Bexp.bool false = y₂ x:Bexpy₁:Bexpb₁'✝:Bexph✝:Bexp.bool true ⟶b b₁'✝⊢ Bexp.bool false = b₁'✝.and (Bexp.bool false)x:Bexpy₁:Bexpb₂'✝:Bexph✝:Bexp.bool false ⟶b b₂'✝⊢ Bexp.bool false = (Bexp.bool true).and b₂'✝x:Bexpy₁:Bexp⊢ Bexp.bool false = Bexp.bool false x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝¹:Aexpa₂✝:Aexph✝¹:a₁✝ ⟶a a₁'✝a₁'✝:Aexph✝:a₁✝ ⟶a a₁'✝⊢ Bexp.eq a₁'✝¹ a₂✝ = Bexp.eq a₁'✝ a₂✝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₂'✝x:Bexpy₁:Bexpa₁'✝:Aexpn₁✝:Natn₂✝:Nath✝:Aexp.num n₁✝ ⟶a a₁'✝⊢ Bexp.eq a₁'✝ (Aexp.num n₂✝) = Bexp.bool (decide (n₁✝ = n₂✝))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₂✝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₂'✝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₂✝))x:Bexpy₁:Bexpn₁✝:Natn₂✝:Nata₁'✝:Aexph✝:Aexp.num n₁✝ ⟶a a₁'✝⊢ Bexp.bool (decide (n₁✝ = n₂✝)) = Bexp.eq a₁'✝ (Aexp.num n₂✝)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₂'✝x:Bexpy₁:Bexpn₁✝:Natn₂✝:Nat⊢ Bexp.bool (decide (n₁✝ = n₂✝)) = Bexp.bool (decide (n₁✝ = n₂✝))x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝¹:Aexpa₂✝:Aexph✝¹:a₁✝ ⟶a a₁'✝a₁'✝:Aexph✝:a₁✝ ⟶a a₁'✝⊢ Bexp.neq a₁'✝¹ a₂✝ = Bexp.neq a₁'✝ a₂✝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₂'✝x:Bexpy₁:Bexpa₁'✝:Aexpn₁✝:Natn₂✝:Nath✝:Aexp.num n₁✝ ⟶a a₁'✝⊢ Bexp.neq a₁'✝ (Aexp.num n₂✝) = Bexp.bool (decide (n₁✝ ≠ n₂✝))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₂✝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₂'✝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₂✝))x:Bexpy₁:Bexpn₁✝:Natn₂✝:Nata₁'✝:Aexph✝:Aexp.num n₁✝ ⟶a a₁'✝⊢ Bexp.bool (decide (n₁✝ ≠ n₂✝)) = Bexp.neq a₁'✝ (Aexp.num n₂✝)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₂'✝x:Bexpy₁:Bexpn₁✝:Natn₂✝:Nat⊢ Bexp.bool (decide (n₁✝ ≠ n₂✝)) = Bexp.bool (decide (n₁✝ ≠ n₂✝))x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝¹:Aexpa₂✝:Aexph✝¹:a₁✝ ⟶a a₁'✝a₁'✝:Aexph✝:a₁✝ ⟶a a₁'✝⊢ Bexp.le a₁'✝¹ a₂✝ = Bexp.le a₁'✝ a₂✝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₂'✝x:Bexpy₁:Bexpa₁'✝:Aexpn₁✝:Natn₂✝:Nath✝:Aexp.num n₁✝ ⟶a a₁'✝⊢ Bexp.le a₁'✝ (Aexp.num n₂✝) = Bexp.bool (decide (n₁✝ ≤ n₂✝))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₂✝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₂'✝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₂✝))x:Bexpy₁:Bexpn₁✝:Natn₂✝:Nata₁'✝:Aexph✝:Aexp.num n₁✝ ⟶a a₁'✝⊢ Bexp.bool (decide (n₁✝ ≤ n₂✝)) = Bexp.le a₁'✝ (Aexp.num n₂✝)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₂'✝x:Bexpy₁:Bexpn₁✝:Natn₂✝:Nat⊢ Bexp.bool (decide (n₁✝ ≤ n₂✝)) = Bexp.bool (decide (n₁✝ ≤ n₂✝))x:Bexpy₁:Bexpa₁✝:Aexpa₁'✝¹:Aexpa₂✝:Aexph✝¹:a₁✝ ⟶a a₁'✝a₁'✝:Aexph✝:a₁✝ ⟶a a₁'✝⊢ Bexp.gt a₁'✝¹ a₂✝ = Bexp.gt a₁'✝ a₂✝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₂'✝x:Bexpy₁:Bexpa₁'✝:Aexpn₁✝:Natn₂✝:Nath✝:Aexp.num n₁✝ ⟶a a₁'✝⊢ Bexp.gt a₁'✝ (Aexp.num n₂✝) = Bexp.bool (decide (n₁✝ > n₂✝))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₂✝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₂'✝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₂✝))x:Bexpy₁:Bexpn₁✝:Natn₂✝:Nata₁'✝:Aexph✝:Aexp.num n₁✝ ⟶a a₁'✝⊢ Bexp.bool (decide (n₁✝ > n₂✝)) = Bexp.gt a₁'✝ (Aexp.num n₂✝)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₂'✝x:Bexpy₁:Bexpn₁✝:Natn₂✝:Nat⊢ Bexp.bool (decide (n₁✝ > n₂✝)) = Bexp.bool (decide (n₁✝ > n₂✝))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₁'✝.notx:Bexpy₁:Bexpb₁'✝:Bexph✝:Bexp.bool true ⟶b b₁'✝h_ih✝:∀ (y₂ : Bexp), Bexp.bool true ⟶b y₂ → b₁'✝ = y₂⊢ b₁'✝.not = Bexp.bool falsex:Bexpy₁:Bexpb₁'✝:Bexph✝:Bexp.bool false ⟶b b₁'✝h_ih✝:∀ (y₂ : Bexp), Bexp.bool false ⟶b y₂ → b₁'✝ = y₂⊢ b₁'✝.not = Bexp.bool truex:Bexpy₁:Bexpb₁'✝:Bexph✝:Bexp.bool true ⟶b b₁'✝⊢ Bexp.bool false = b₁'✝.notx:Bexpy₁:Bexp⊢ Bexp.bool false = Bexp.bool falsex:Bexpy₁:Bexpb₁'✝:Bexph✝:Bexp.bool false ⟶b b₁'✝⊢ Bexp.bool true = b₁'✝.notx:Bexpy₁:Bexp⊢ Bexp.bool true = Bexp.bool truex: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₂✝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₂'✝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 falsex: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 truex: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 falsex: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₂✝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₂'✝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 truex: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 falsex:Bexpy₁:Bexpb₂✝:Bexpb₁'✝:Bexph✝:Bexp.bool false ⟶b b₁'✝⊢ Bexp.bool false = b₁'✝.and b₂✝x:Bexpy₁:Bexpb₂✝:Bexp⊢ Bexp.bool false = Bexp.bool falsex:Bexpy₁:Bexpb₁'✝:Bexph✝:Bexp.bool true ⟶b b₁'✝⊢ Bexp.bool true = b₁'✝.and (Bexp.bool true)x:Bexpy₁:Bexpb₂'✝:Bexph✝:Bexp.bool true ⟶b b₂'✝⊢ Bexp.bool true = (Bexp.bool true).and b₂'✝x:Bexpy₁:Bexp⊢ Bexp.bool true = Bexp.bool truex:Bexpy₁:Bexpb₁'✝:Bexph✝:Bexp.bool true ⟶b b₁'✝⊢ Bexp.bool false = b₁'✝.and (Bexp.bool false)x:Bexpy₁:Bexpb₂'✝:Bexph✝:Bexp.bool false ⟶b b₂'✝⊢ Bexp.bool false = (Bexp.bool true).and b₂'✝x:Bexpy₁:Bexp⊢ Bexp.bool false = Bexp.bool false first | All goals completed! 🐙 | x:Bexpy₁:Bexpb₂'✝:Bexph✝:Bexp.bool false ⟶b b₂'✝⊢ Bexp.bool false = (Bexp.bool true).and b₂'✝ | (x:Bexpy₁:Bexpb₂'✝:Bexph✝:Bexp.bool false ⟶b b₂'✝⊢ Bexp.bool false = (Bexp.bool true).and b₂'✝; 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₂'✝) | All goals completed! 🐙 | (x:Bexpy₁:Bexpb₂✝:Bexpb₂'✝¹:Bexph✝¹:b₂✝ ⟶b b₂'✝h_ih✝:∀ (y₂ : Bexp), b₂✝ ⟶b y₂ → b₂'✝ = y₂b₂'✝:Bexph✝:b₂✝ ⟶b b₂'✝⊢ b₂'✝¹ = 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 | x:Bexpy₁:Bexpb₂✝:Bexpb₂'✝¹:Bexph✝¹:b₂✝ ⟶b b₂'✝h_ih✝:∀ (y₂ : Bexp), b₂✝ ⟶b y₂ → b₂'✝ = y₂b₂'✝:Bexph✝:b₂✝ ⟶b b₂'✝⊢ b₂'✝¹ = 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₂'✝ 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₂'✝¹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₂'✝x:Bexpy₁:Bexpv₁✝:Aexpa₂✝:Aexpa₂'✝¹:Aexphv✝¹:IsAValue v₁✝h✝¹:a₂✝ ⟶a a₂'✝a₂'✝:Aexphv✝:IsAValue v₁✝h✝:a₂✝ ⟶a a₂'✝⊢ Aexp All goals completed! 🐙) | (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₂'✝ 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₂'✝ 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 := ⊢ ¬Deterministic ANStep hd:Deterministic ANStep⊢ False 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 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 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 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.

Exercise★★(anstep_preserves_eval)

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 := a:Aexpa':Aexph:a ⟶n a'⊢ a.eval = a'.eval solution! a:Aexpa':Aexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶n a₁'✝h_ih✝:a₁✝.eval = a₁'✝.eval⊢ (a₁✝.plus a₂✝).eval = (a₁'✝.plus a₂✝).evala:Aexpa':Aexpa₁✝:Aexpa₂✝:Aexpa₂'✝:Aexph✝:a₂✝ ⟶n a₂'✝h_ih✝:a₂✝.eval = a₂'✝.eval⊢ (a₁✝.plus a₂✝).eval = (a₁✝.plus a₂'✝).evala:Aexpa':Aexpn₁✝:Natn₂✝:Nat⊢ ((Aexp.num n₁✝).plus (Aexp.num n₂✝)).eval = (Aexp.num (n₁✝ + n₂✝)).evala:Aexpa':Aexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶n a₁'✝h_ih✝:a₁✝.eval = a₁'✝.eval⊢ (a₁✝.minus a₂✝).eval = (a₁'✝.minus a₂✝).evala:Aexpa':Aexpa₁✝:Aexpa₂✝:Aexpa₂'✝:Aexph✝:a₂✝ ⟶n a₂'✝h_ih✝:a₂✝.eval = a₂'✝.eval⊢ (a₁✝.minus a₂✝).eval = (a₁✝.minus a₂'✝).evala:Aexpa':Aexpn₁✝:Natn₂✝:Nat⊢ ((Aexp.num n₁✝).minus (Aexp.num n₂✝)).eval = (Aexp.num (n₁✝ - n₂✝)).evala:Aexpa':Aexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶n a₁'✝h_ih✝:a₁✝.eval = a₁'✝.eval⊢ (a₁✝.mult a₂✝).eval = (a₁'✝.mult a₂✝).evala:Aexpa':Aexpa₁✝:Aexpa₂✝:Aexpa₂'✝:Aexph✝:a₂✝ ⟶n a₂'✝h_ih✝:a₂✝.eval = a₂'✝.eval⊢ (a₁✝.mult a₂✝).eval = (a₁✝.mult a₂'✝).evala:Aexpa':Aexpn₁✝:Natn₂✝:Nat⊢ ((Aexp.num n₁✝).mult (Aexp.num n₂✝)).eval = (Aexp.num (n₁✝ * n₂✝)).eval a:Aexpa':Aexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶n a₁'✝h_ih✝:a₁✝.eval = a₁'✝.eval⊢ (a₁✝.plus a₂✝).eval = (a₁'✝.plus a₂✝).evala:Aexpa':Aexpa₁✝:Aexpa₂✝:Aexpa₂'✝:Aexph✝:a₂✝ ⟶n a₂'✝h_ih✝:a₂✝.eval = a₂'✝.eval⊢ (a₁✝.plus a₂✝).eval = (a₁✝.plus a₂'✝).evala:Aexpa':Aexpn₁✝:Natn₂✝:Nat⊢ ((Aexp.num n₁✝).plus (Aexp.num n₂✝)).eval = (Aexp.num (n₁✝ + n₂✝)).evala:Aexpa':Aexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶n a₁'✝h_ih✝:a₁✝.eval = a₁'✝.eval⊢ (a₁✝.minus a₂✝).eval = (a₁'✝.minus a₂✝).evala:Aexpa':Aexpa₁✝:Aexpa₂✝:Aexpa₂'✝:Aexph✝:a₂✝ ⟶n a₂'✝h_ih✝:a₂✝.eval = a₂'✝.eval⊢ (a₁✝.minus a₂✝).eval = (a₁✝.minus a₂'✝).evala:Aexpa':Aexpn₁✝:Natn₂✝:Nat⊢ ((Aexp.num n₁✝).minus (Aexp.num n₂✝)).eval = (Aexp.num (n₁✝ - n₂✝)).evala:Aexpa':Aexpa₁✝:Aexpa₁'✝:Aexpa₂✝:Aexph✝:a₁✝ ⟶n a₁'✝h_ih✝:a₁✝.eval = a₁'✝.eval⊢ (a₁✝.mult a₂✝).eval = (a₁'✝.mult a₂✝).evala:Aexpa':Aexpa₁✝:Aexpa₂✝:Aexpa₂'✝:Aexph✝:a₂✝ ⟶n a₂'✝h_ih✝:a₂✝.eval = a₂'✝.eval⊢ (a₁✝.mult a₂✝).eval = (a₁✝.mult a₂'✝).evala:Aexpa':Aexpn₁✝:Natn₂✝:Nat⊢ ((Aexp.num n₁✝).mult (Aexp.num n₂✝)).eval = (Aexp.num (n₁✝ * n₂✝)).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 := a:Aexpa':Aexph:Multi ANStep a a'⊢ a.eval = a'.eval induction h with a:Aexpa':Aexpx:Aexp⊢ x.eval = x.eval All goals completed! 🐙 a:Aexpa':Aexpx:Aexpy:Aexpz:Aexph₁:x ⟶n yh₂✝:Multi ANStep y zih:y.eval = z.eval⊢ x.eval = z.eval a:Aexpa':Aexpx:Aexpy:Aexpz:Aexph₁:x ⟶n yh₂✝:Multi ANStep y zih:y.eval = z.eval⊢ y.eval = z.eval; 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' := a:Aexpa':Aexph:a ⟶a a'⊢ a ⟶n a' induction h with a:Aexpa':Aexpa₁:Aexpa₁':Aexpa₂:Aexph✝:a₁ ⟶a a₁'ih:a₁ ⟶n a₁'⊢ a₁.plus a₂ ⟶n a₁'.plus a₂ All goals completed! 🐙 a:Aexpa':Aexpv₁:Aexpa₂:Aexpa₂':Aexphv✝:IsAValue v₁h✝:a₂ ⟶a a₂'ih:a₂ ⟶n a₂'⊢ v₁.plus a₂ ⟶n v₁.plus a₂' All goals completed! 🐙 a:Aexpa':Aexpn₁:Natn₂:Nat⊢ (Aexp.num n₁).plus (Aexp.num n₂) ⟶n Aexp.num (n₁ + n₂) All goals completed! 🐙 a:Aexpa':Aexpa₁:Aexpa₁':Aexpa₂:Aexph✝:a₁ ⟶a a₁'ih:a₁ ⟶n a₁'⊢ a₁.minus a₂ ⟶n a₁'.minus a₂ All goals completed! 🐙 a:Aexpa':Aexpv₁:Aexpa₂:Aexpa₂':Aexphv✝:IsAValue v₁h✝:a₂ ⟶a a₂'ih:a₂ ⟶n a₂'⊢ v₁.minus a₂ ⟶n v₁.minus a₂' All goals completed! 🐙 a:Aexpa':Aexpn₁:Natn₂:Nat⊢ (Aexp.num n₁).minus (Aexp.num n₂) ⟶n Aexp.num (n₁ - n₂) All goals completed! 🐙 a:Aexpa':Aexpa₁:Aexpa₁':Aexpa₂:Aexph✝:a₁ ⟶a a₁'ih:a₁ ⟶n a₁'⊢ a₁.mult a₂ ⟶n a₁'.mult a₂ All goals completed! 🐙 a:Aexpa':Aexpv₁:Aexpa₂:Aexpa₂':Aexphv✝:IsAValue v₁h✝:a₂ ⟶a a₂'ih:a₂ ⟶n a₂'⊢ v₁.mult a₂ ⟶n v₁.mult a₂' All goals completed! 🐙 a:Aexpa':Aexpn₁:Natn₂:Nat⊢ (Aexp.num n₁).mult (Aexp.num n₂) ⟶n Aexp.num (n₁ * n₂) All goals completed! 🐙 theorem multi_astep_imp_anstep (a a' : Aexp) (h : Multi AStep a a') : Multi ANStep a a' := a:Aexpa':Aexph:Multi AStep a a'⊢ Multi ANStep a a' induction h with a:Aexpa':Aexpx:Aexp⊢ Multi ANStep x x All goals completed! 🐙 a:Aexpa':Aexpx:Aexpy:Aexpz:Aexph₁:x ⟶a yh₂✝:Multi AStep y zih:Multi ANStep y z⊢ Multi ANStep x z All goals completed! 🐙
Exercise★★★(astep_anstep_agree)

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₂ := a:Aexpn₁:Natn₂:Nathd:Multi AStep a (Aexp.num n₁)hn:Multi ANStep a (Aexp.num n₂)⊢ n₁ = n₂ solution! 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₂ 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₂ 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₂ 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 := ⊢ Deterministic StackStep x:Prog × Stacky₁:Prog × Stacky₂:Prog × Stackh₁:StackStep x y₁h₂:StackStep x y₂⊢ y₁ = y₂ y₂:Prog × Stackp✝:Progstk✝:Stackn✝:Nath₂:StackStep (SInstr.push n✝ :: p✝, stk✝) y₂⊢ (p✝, n✝ :: stk✝) = y₂y₂:Prog × Stackp✝:Progstk✝:Stackn✝:Natm✝:Nath₂:StackStep (SInstr.plus :: p✝, n✝ :: m✝ :: stk✝) y₂⊢ (p✝, (m✝ + n✝) :: stk✝) = y₂y₂:Prog × Stackp✝:Progstk✝:Stackn✝:Natm✝:Nath₂:StackStep (SInstr.minus :: p✝, n✝ :: m✝ :: stk✝) y₂⊢ (p✝, (m✝ - n✝) :: stk✝) = y₂y₂:Prog × Stackp✝:Progstk✝:Stackn✝:Natm✝:Nath₂:StackStep (SInstr.mult :: p✝, n✝ :: m✝ :: stk✝) y₂⊢ (p✝, m✝ * n✝ :: stk✝) = y₂ y₂:Prog × Stackp✝:Progstk✝:Stackn✝:Nath₂:StackStep (SInstr.push n✝ :: p✝, stk✝) y₂⊢ (p✝, n✝ :: stk✝) = y₂y₂:Prog × Stackp✝:Progstk✝:Stackn✝:Natm✝:Nath₂:StackStep (SInstr.plus :: p✝, n✝ :: m✝ :: stk✝) y₂⊢ (p✝, (m✝ + n✝) :: stk✝) = y₂y₂:Prog × Stackp✝:Progstk✝:Stackn✝:Natm✝:Nath₂:StackStep (SInstr.minus :: p✝, n✝ :: m✝ :: stk✝) y₂⊢ (p✝, (m✝ - n✝) :: stk✝) = y₂y₂:Prog × Stackp✝:Progstk✝:Stackn✝:Natm✝:Nath₂:StackStep (SInstr.mult :: p✝, n✝ :: m✝ :: stk✝) y₂⊢ (p✝, m✝ * n✝ :: stk✝) = y₂ p✝:Progstk✝:Stackn✝:Natm✝:Nat⊢ (p✝, m✝ * n✝ :: stk✝) = (p✝, m✝ * n✝ :: stk✝) p✝:Progstk✝:Stackn✝:Nat⊢ (p✝, n✝ :: stk✝) = (p✝, n✝ :: stk✝)p✝:Progstk✝:Stackn✝:Natm✝:Nat⊢ (p✝, (m✝ + n✝) :: stk✝) = (p✝, (m✝ + n✝) :: stk✝)p✝:Progstk✝:Stackn✝:Natm✝:Nat⊢ (p✝, (m✝ - n✝) :: stk✝) = (p✝, (m✝ - n✝) :: stk✝)p✝:Progstk✝:Stackn✝:Natm✝:Nat⊢ (p✝, m✝ * n✝ :: stk✝) = (p✝, m✝ * n✝ :: stk✝) All goals completed! 🐙
Exercise★★★(compiler_is_correct) (Advanced)

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]) := a:Aexp⊢ Multi StackStep (compile a, []) ([], [a.eval]) solution! a:Aexpgen:∀ (a : Aexp) (p : Prog) (stk : Stack), Multi StackStep (compile a ++ p, stk) (p, a.eval :: stk)⊢ Multi StackStep (compile a, []) ([], [a.eval]) 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]) 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]) 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) := ⊢ (Tm.c 3).p ((Tm.c 3).p (Tm.c 4)) ⟶* Tm.c 10 ⊢ (Tm.c 3).p ((Tm.c 3).p (Tm.c 4)) ⟶ (Tm.c 3).p (Tm.c 7)⊢ (Tm.c 3).p (Tm.c 7) ⟶* Tm.c 10 ⊢ (Tm.c 3).p ((Tm.c 3).p (Tm.c 4)) ⟶ (Tm.c 3).p (Tm.c 7) ⊢ IsValue (Tm.c 3)⊢ (Tm.c 3).p (Tm.c 4) ⟶ Tm.c 7 ⊢ IsValue (Tm.c 3) All goals completed! 🐙 ⊢ (Tm.c 3).p (Tm.c 4) ⟶ Tm.c 7 All goals completed! 🐙 ⊢ (Tm.c 3).p (Tm.c 7) ⟶* Tm.c 10 ⊢ (Tm.c 3).p (Tm.c 7) ⟶ Tm.c 10⊢ Tm.c 10 ⟶* Tm.c 10 ⊢ (Tm.c 3).p (Tm.c 7) ⟶ Tm.c 10 All goals completed! 🐙 ⊢ Tm.c 10 ⟶* Tm.c 10 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) := ⊢ (Tm.c 3).p ((Tm.c 3).p (Tm.c 4)) ⟶* Tm.c 10 repeat ⊢ (Tm.c 3).p (Tm.c (3 + 4)) ⟶ ?h₂.y⊢ ?h₂.y ⟶* Tm.c 10⊢ Tm ⊢ (Tm.c 3).p (Tm.c (3 + 4)) ⟶ ?h₂.y⊢ ?h₂.y ⟶* Tm.c 10⊢ Tm try 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) := ⊢ (Tm.c 3).p ((Tm.c 3).p (Tm.c 4)) ⟶* Tm.c 10 repeat ⊢ (Tm.c 3).p (Tm.c (3 + 4)) ⟶ ?h₂.y⊢ ?h₂.y ⟶* Tm.c 10⊢ Tm ⊢ (Tm.c 3).p (Tm.c (3 + 4)) ⟶ ?h₂.y⊢ ?h₂.y ⟶* Tm.c 10⊢ Tm try 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) := ⊢ (Tm.c 3).p ((Tm.c 3).p (Tm.c 4)) ⟶* Tm.c 10 All goals completed! 🐙
Exercise★(normalize_ex)

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' := ⊢ ∃ e', (Tm.c 3).p ((Tm.c 2).p (Tm.c 1)) ⟶* e' ∧ IsValue e' solution! ⊢ (Tm.c 3).p ((Tm.c 2).p (Tm.c 1)) ⟶* Tm.c 6 ∧ IsValue (Tm.c 6); ⊢ (Tm.c 3).p ((Tm.c 2).p (Tm.c 1)) ⟶* Tm.c 6⊢ IsValue (Tm.c 6) ⊢ (Tm.c 3).p ((Tm.c 2).p (Tm.c 1)) ⟶* Tm.c 6 All goals completed! 🐙 ⊢ IsValue (Tm.c 6) All goals completed! 🐙
Source revision: 9cc9a7b, committed 2026-09-28 19:09 UTC