Turing machines #
The files PostTuringMachine.lean
and TuringMachine.lean
define
a sequence of simple machine languages, starting with Turing machines and working
up to more complex languages based on Wang B-machines.
PostTuringMachine.lean
covers the TM0 model and TM1 model;
TuringMachine.lean
adds the TM2 model.
Naming conventions #
Each model of computation in this file shares a naming convention for the elements of a model of computation. These are the parameters for the language:
Γ
is the alphabet on the tape.Λ
is the set of labels, or internal machine states.σ
is the type of internal memory, not on the tape. This does not exist in the TM0 model, and later models achieve this by mixing it intoΛ
.K
is used in the TM2 model, which has multiple stacks, and denotes the number of such stacks.
All of these variables denote "essentially finite" types, but for technical reasons it is convenient to allow them to be infinite anyway. When using an infinite type, we will be interested to prove that only finitely many values of the type are ever interacted with.
Given these parameters, there are a few common structures for the model that arise:
Stmt
is the set of all actions that can be performed in one step. For the TM0 model this set is finite, and for later models it is an infinite inductive type representing "possible program texts".Cfg
is the set of instantaneous configurations, that is, the state of the machine together with its environment.Machine
is the set of all machines in the model. Usually this is approximately a functionΛ → Stmt
, although different models have different ways of halting and other actions.step : Cfg → Option Cfg
is the function that describes how the state evolves over one step. Ifstep c = none
, thenc
is a terminal state, and the result of the computation is read off fromc
. Because of the type ofstep
, these models are all deterministic by construction.init : Input → Cfg
sets up the initial state. The typeInput
depends on the model; in most cases it isList Γ
.eval : Machine → Input → Part Output
, given a machineM
and inputi
, starts frominit i
, runsstep
until it reaches an output, and then applies a functionCfg → Output
to the final state to obtain the result. The typeOutput
depends on the model.Supports : Machine → Finset Λ → Prop
asserts that a machineM
starts inS : Finset Λ
, and can only ever jump to other states insideS
. This implies that the behavior ofM
on any input cannot depend on its values outsideS
. We use this to allowΛ
to be an infinite set when convenient, and prove that only finitely many of these states are actually accessible. This formalizes "essentially finite" mentioned above.
The TM2 model #
The TM2 model removes the tape entirely from the TM1 model, replacing it with an arbitrary (finite)
collection of stacks, each with elements of different types (the alphabet of stack k : K
is
Γ k
). The statements are:
push k (f : σ → Γ k) q
putsf a
on thek
-th stack, then doesq
.pop k (f : σ → Option (Γ k) → σ) q
changes the state tof a (S k).head
, whereS k
is the value of thek
-th stack, and removes this element from the stack, then doesq
.peek k (f : σ → Option (Γ k) → σ) q
changes the state tof a (S k).head
, whereS k
is the value of thek
-th stack, then doesq
.load (f : σ → σ) q
reads nothing but appliesf
to the internal state, then doesq
.branch (f : σ → Bool) qtrue qfalse
doesqtrue
orqfalse
according tof a
.goto (f : σ → Λ)
jumps to labelf a
.halt
halts on the next step.
The configuration is a tuple (l, var, stk)
where l : Option Λ
is the current label to run or
none
for the halting state, var : σ
is the (finite) internal state, and stk : ∀ k, List (Γ k)
is the collection of stacks. (Note that unlike the TM0
and TM1
models, these are not
ListBlank
s, they have definite ends that can be detected by the pop
command.)
Given a designated stack k
and a value L : List (Γ k)
, the initial configuration has all the
stacks empty except the designated "input" stack; in eval
this designated stack also functions
as the output stack.
The TM2 model removes the tape entirely from the TM1 model,
replacing it with an arbitrary (finite) collection of stacks.
The operation push
puts an element on one of the stacks,
and pop
removes an element from a stack (and modifying the
internal state based on the result). peek
modifies the
internal state but does not remove an element.
- push {K : Type u_1} {Γ : K → Type u_2} {Λ : Type u_3} {σ : Type u_4} (k : K) : (σ → Γ k) → Stmt Γ Λ σ → Stmt Γ Λ σ
- peek {K : Type u_1} {Γ : K → Type u_2} {Λ : Type u_3} {σ : Type u_4} (k : K) : (σ → Option (Γ k) → σ) → Stmt Γ Λ σ → Stmt Γ Λ σ
- pop {K : Type u_1} {Γ : K → Type u_2} {Λ : Type u_3} {σ : Type u_4} (k : K) : (σ → Option (Γ k) → σ) → Stmt Γ Λ σ → Stmt Γ Λ σ
- load {K : Type u_1} {Γ : K → Type u_2} {Λ : Type u_3} {σ : Type u_4} : (σ → σ) → Stmt Γ Λ σ → Stmt Γ Λ σ
- branch {K : Type u_1} {Γ : K → Type u_2} {Λ : Type u_3} {σ : Type u_4} : (σ → Bool) → Stmt Γ Λ σ → Stmt Γ Λ σ → Stmt Γ Λ σ
- goto {K : Type u_1} {Γ : K → Type u_2} {Λ : Type u_3} {σ : Type u_4} : (σ → Λ) → Stmt Γ Λ σ
- halt {K : Type u_1} {Γ : K → Type u_2} {Λ : Type u_3} {σ : Type u_4} : Stmt Γ Λ σ
Instances For
Equations
- Turing.TM2.Stmt.inhabited Γ Λ σ = { default := Turing.TM2.Stmt.halt }
A configuration in the TM2 model is a label (or none
for the halt state), the state of
local variables, and the stacks. (Note that the stacks are not ListBlank
s, they have a definite
size.)
- l : Option Λ
The current label to run (or
none
for the halting state) - var : σ
The internal state
- stk (k : K) : List (Γ k)
The (finite) collection of internal stacks
Instances For
The step function for the TM2 model.
Equations
- Turing.TM2.stepAux (Turing.TM2.Stmt.push k f q) x✝¹ x✝ = Turing.TM2.stepAux q x✝¹ (Function.update x✝ k (f x✝¹ :: x✝ k))
- Turing.TM2.stepAux (Turing.TM2.Stmt.peek k f q) x✝¹ x✝ = Turing.TM2.stepAux q (f x✝¹ (x✝ k).head?) x✝
- Turing.TM2.stepAux (Turing.TM2.Stmt.pop k f q) x✝¹ x✝ = Turing.TM2.stepAux q (f x✝¹ (x✝ k).head?) (Function.update x✝ k (x✝ k).tail)
- Turing.TM2.stepAux (Turing.TM2.Stmt.load a q) x✝¹ x✝ = Turing.TM2.stepAux q (a x✝¹) x✝
- Turing.TM2.stepAux (Turing.TM2.Stmt.branch f q₁ q₂) x✝¹ x✝ = bif f x✝¹ then Turing.TM2.stepAux q₁ x✝¹ x✝ else Turing.TM2.stepAux q₂ x✝¹ x✝
- Turing.TM2.stepAux (Turing.TM2.Stmt.goto f) x✝¹ x✝ = { l := some (f x✝¹), var := x✝¹, stk := x✝ }
- Turing.TM2.stepAux Turing.TM2.Stmt.halt x✝¹ x✝ = { l := none, var := x✝¹, stk := x✝ }
Instances For
The step function for the TM2 model.
Equations
- Turing.TM2.step M { l := none, var := var, stk := stk } = none
- Turing.TM2.step M { l := some l, var := v, stk := S } = some (Turing.TM2.stepAux (M l) v S)
Instances For
The (reflexive) reachability relation for the TM2 model.
Equations
- Turing.TM2.Reaches M = Relation.ReflTransGen fun (a b : Turing.TM2.Cfg Γ Λ σ) => b ∈ Turing.TM2.step M a
Instances For
Given a set S
of states, SupportsStmt S q
means that q
only jumps to states in S
.
Equations
- Turing.TM2.SupportsStmt S (Turing.TM2.Stmt.push k a q) = Turing.TM2.SupportsStmt S q
- Turing.TM2.SupportsStmt S (Turing.TM2.Stmt.peek k a q) = Turing.TM2.SupportsStmt S q
- Turing.TM2.SupportsStmt S (Turing.TM2.Stmt.pop k a q) = Turing.TM2.SupportsStmt S q
- Turing.TM2.SupportsStmt S (Turing.TM2.Stmt.load a q) = Turing.TM2.SupportsStmt S q
- Turing.TM2.SupportsStmt S (Turing.TM2.Stmt.branch a q₁ q₂) = (Turing.TM2.SupportsStmt S q₁ ∧ Turing.TM2.SupportsStmt S q₂)
- Turing.TM2.SupportsStmt S (Turing.TM2.Stmt.goto l) = ∀ (v : σ), l v ∈ S
- Turing.TM2.SupportsStmt S Turing.TM2.Stmt.halt = True
Instances For
The set of subtree statements in a statement.
Equations
- Turing.TM2.stmts₁ (Turing.TM2.Stmt.push k a q) = insert (Turing.TM2.Stmt.push k a q) (Turing.TM2.stmts₁ q)
- Turing.TM2.stmts₁ (Turing.TM2.Stmt.peek k a q) = insert (Turing.TM2.Stmt.peek k a q) (Turing.TM2.stmts₁ q)
- Turing.TM2.stmts₁ (Turing.TM2.Stmt.pop k a q) = insert (Turing.TM2.Stmt.pop k a q) (Turing.TM2.stmts₁ q)
- Turing.TM2.stmts₁ (Turing.TM2.Stmt.load a q) = insert (Turing.TM2.Stmt.load a q) (Turing.TM2.stmts₁ q)
- Turing.TM2.stmts₁ (Turing.TM2.Stmt.branch a q₁ q₂) = insert (Turing.TM2.Stmt.branch a q₁ q₂) (Turing.TM2.stmts₁ q₁ ∪ Turing.TM2.stmts₁ q₂)
- Turing.TM2.stmts₁ (Turing.TM2.Stmt.goto a) = {Turing.TM2.Stmt.goto a}
- Turing.TM2.stmts₁ Turing.TM2.Stmt.halt = {Turing.TM2.Stmt.halt}
Instances For
The set of statements accessible from initial set S
of labels.
Equations
- Turing.TM2.stmts M S = Finset.insertNone (S.biUnion fun (q : Λ) => Turing.TM2.stmts₁ (M q))
Instances For
Given a TM2 machine M
and a set S
of states, Supports M S
means that all states in
S
jump only to other states in S
.
Equations
- Turing.TM2.Supports M S = (default ∈ S ∧ ∀ q ∈ S, Turing.TM2.SupportsStmt S (M q))
Instances For
The initial state of the TM2 model. The input is provided on a designated stack.
Equations
- Turing.TM2.init k L = { l := some default, var := default, stk := Function.update (fun (x : K) => []) k L }
Instances For
Evaluates a TM2 program to completion, with the output on the same stack as the input.
Equations
- Turing.TM2.eval M k L = Part.map (fun (c : Turing.TM2.Cfg Γ Λ σ) => c.stk k) (Turing.eval (Turing.TM2.step M) (Turing.TM2.init k L))
Instances For
TM2 emulator in TM1 #
To prove that TM2 computable functions are TM1 computable, we need to reduce each TM2 program to a
TM1 program. So suppose a TM2 program is given. This program has to maintain a whole collection of
stacks, but we have only one tape, so we must "multiplex" them all together. Pictorially, if stack
1 contains [a, b]
and stack 2 contains [c, d, e, f]
then the tape looks like this:
bottom: ... | _ | T | _ | _ | _ | _ | ...
stack 1: ... | _ | b | a | _ | _ | _ | ...
stack 2: ... | _ | f | e | d | c | _ | ...
where a tape element is a vertical slice through the diagram. Here the alphabet is
Γ' := Bool × ∀ k, Option (Γ k)
, where:
bottom : Bool
is marked only in one place, the initial position of the TM, and represents the tail of all stacks. It is never modified.stk k : Option (Γ k)
is the value of thek
-th stack, if in range, otherwisenone
(which is the blank value). Note that the head of the stack is at the far end; this is so that push and pop don't have to do any shifting.
In "resting" position, the TM is sitting at the position marked bottom
. For non-stack actions,
it operates in place, but for the stack actions push
, peek
, and pop
, it must shuttle to the
end of the appropriate stack, make its changes, and then return to the bottom. So the states are:
normal (l : Λ)
: waiting atbottom
to execute functionl
go k (s : StAct k) (q : Stmt₂)
: travelling to the right to get to the end of stackk
in order to perform stack actions
, and later continue with executingq
ret (q : Stmt₂)
: travelling to the left after having performed a stack action, and executingq
once we arrive
Because of the shuttling, emulation overhead is O(n)
, where n
is the current maximum of the
length of all stacks. Therefore a program that takes k
steps to run in TM2 takes O((m+k)k)
steps to run when emulated in TM1, where m
is the length of the input.
The alphabet of the TM2 simulator on TM1 is a marker for the stack bottom, plus a vector of stack elements for each stack, or none if the stack does not extend this far.
Equations
- Turing.TM2to1.Γ' K Γ = (Bool × ((k : K) → Option (Γ k)))
Instances For
Equations
- Turing.TM2to1.Γ'.fintype = instFintypeProd Bool ((k : K) → Option (Γ k))
The bottom marker is fixed throughout the calculation, so we use the addBottom
function
to express the program state in terms of a tape with only the stacks themselves.
Equations
- Turing.TM2to1.addBottom L = Turing.ListBlank.cons (true, L.head) (Turing.ListBlank.map { f := Prod.mk false, map_pt' := ⋯ } L.tail)
Instances For
A stack action is a command that interacts with the top of a stack. Our default position is at the bottom of all the stacks, so we have to hold on to this action while going to the end to modify the stack.
- push {K : Type u_1} {Γ : K → Type u_2} {σ : Type u_4} {k : K} : (σ → Γ k) → StAct K Γ σ k
- peek {K : Type u_1} {Γ : K → Type u_2} {σ : Type u_4} {k : K} : (σ → Option (Γ k) → σ) → StAct K Γ σ k
- pop {K : Type u_1} {Γ : K → Type u_2} {σ : Type u_4} {k : K} : (σ → Option (Γ k) → σ) → StAct K Γ σ k
Instances For
Equations
- Turing.TM2to1.StAct.inhabited = { default := Turing.TM2to1.StAct.peek fun (s : σ) (x : Option (Γ k)) => s }
The TM2 statement corresponding to a stack action.
Equations
Instances For
The effect of a stack action on the local variables, given the value of the stack.
Equations
- Turing.TM2to1.stVar v l (Turing.TM2to1.StAct.push f) = v
- Turing.TM2to1.stVar v l (Turing.TM2to1.StAct.peek f) = f v l.head?
- Turing.TM2to1.stVar v l (Turing.TM2to1.StAct.pop f) = f v l.head?
Instances For
The effect of a stack action on the stack.
Equations
- Turing.TM2to1.stWrite v l (Turing.TM2to1.StAct.push f) = f v :: l
- Turing.TM2to1.stWrite v l (Turing.TM2to1.StAct.peek f) = l
- Turing.TM2to1.stWrite v l (Turing.TM2to1.StAct.pop f) = l.tail
Instances For
We have partitioned the TM2 statements into "stack actions", which require going to the end of the stack, and all other actions, which do not. This is a modified recursor which lumps the stack actions into one.
Equations
- Turing.TM2to1.stmtStRec H₁ H₂ H₃ H₄ H₅ (Turing.TM2.Stmt.push k a q) = H₁ k (Turing.TM2to1.StAct.push a) q (Turing.TM2to1.stmtStRec H₁ H₂ H₃ H₄ H₅ q)
- Turing.TM2to1.stmtStRec H₁ H₂ H₃ H₄ H₅ (Turing.TM2.Stmt.peek k a q) = H₁ k (Turing.TM2to1.StAct.peek a) q (Turing.TM2to1.stmtStRec H₁ H₂ H₃ H₄ H₅ q)
- Turing.TM2to1.stmtStRec H₁ H₂ H₃ H₄ H₅ (Turing.TM2.Stmt.pop k a q) = H₁ k (Turing.TM2to1.StAct.pop a) q (Turing.TM2to1.stmtStRec H₁ H₂ H₃ H₄ H₅ q)
- Turing.TM2to1.stmtStRec H₁ H₂ H₃ H₄ H₅ (Turing.TM2.Stmt.load a q) = H₂ a q (Turing.TM2to1.stmtStRec H₁ H₂ H₃ H₄ H₅ q)
- Turing.TM2to1.stmtStRec H₁ H₂ H₃ H₄ H₅ (Turing.TM2.Stmt.branch a q₁ q₂) = H₃ a q₁ q₂ (Turing.TM2to1.stmtStRec H₁ H₂ H₃ H₄ H₅ q₁) (Turing.TM2to1.stmtStRec H₁ H₂ H₃ H₄ H₅ q₂)
- Turing.TM2to1.stmtStRec H₁ H₂ H₃ H₄ H₅ (Turing.TM2.Stmt.goto l) = H₄ l
- Turing.TM2to1.stmtStRec H₁ H₂ H₃ H₄ H₅ Turing.TM2.Stmt.halt = H₅
Instances For
The machine states of the TM2 emulator. We can either be in a normal state when waiting for the next TM2 action, or we can be in the "go" and "return" states to go to the top of the stack and return to the bottom, respectively.
- normal {K : Type u_1} {Γ : K → Type u_2} {Λ : Type u_3} {σ : Type u_4} : Λ → Λ' K Γ Λ σ
- go {K : Type u_1} {Γ : K → Type u_2} {Λ : Type u_3} {σ : Type u_4} (k : K) : StAct K Γ σ k → TM2.Stmt Γ Λ σ → Λ' K Γ Λ σ
- ret {K : Type u_1} {Γ : K → Type u_2} {Λ : Type u_3} {σ : Type u_4} : TM2.Stmt Γ Λ σ → Λ' K Γ Λ σ
Instances For
Equations
- Turing.TM2to1.Λ'.inhabited = { default := Turing.TM2to1.Λ'.normal default }
The program corresponding to state transitions at the end of a stack. Here we start out just after the top of the stack, and should end just after the new top of the stack.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The initial state for the TM2 emulator, given an initial TM2 state. All stacks start out empty except for the input stack, and the stack bottom mark is set at the head.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The translation of TM2 statements to TM1 statements. regular actions have direct equivalents,
but stack actions are deferred by going to the corresponding go
state, so that we can find the
appropriate stack top.
Equations
- Turing.TM2to1.trNormal (Turing.TM2.Stmt.push k a q) = Turing.TM1.Stmt.goto fun (x : Turing.TM2to1.Γ' K Γ) (x : σ) => Turing.TM2to1.Λ'.go k (Turing.TM2to1.StAct.push a) q
- Turing.TM2to1.trNormal (Turing.TM2.Stmt.peek k a q) = Turing.TM1.Stmt.goto fun (x : Turing.TM2to1.Γ' K Γ) (x : σ) => Turing.TM2to1.Λ'.go k (Turing.TM2to1.StAct.peek a) q
- Turing.TM2to1.trNormal (Turing.TM2.Stmt.pop k a q) = Turing.TM1.Stmt.goto fun (x : Turing.TM2to1.Γ' K Γ) (x : σ) => Turing.TM2to1.Λ'.go k (Turing.TM2to1.StAct.pop a) q
- Turing.TM2to1.trNormal (Turing.TM2.Stmt.load a q) = Turing.TM1.Stmt.load (fun (x : Turing.TM2to1.Γ' K Γ) => a) (Turing.TM2to1.trNormal q)
- Turing.TM2to1.trNormal (Turing.TM2.Stmt.branch a q₁ q₂) = Turing.TM1.Stmt.branch (fun (x : Turing.TM2to1.Γ' K Γ) => a) (Turing.TM2to1.trNormal q₁) (Turing.TM2to1.trNormal q₂)
- Turing.TM2to1.trNormal (Turing.TM2.Stmt.goto l) = Turing.TM1.Stmt.goto fun (x : Turing.TM2to1.Γ' K Γ) (s : σ) => Turing.TM2to1.Λ'.normal (l s)
- Turing.TM2to1.trNormal Turing.TM2.Stmt.halt = Turing.TM1.Stmt.halt
Instances For
The set of machine states accessible from an initial TM2 statement.
Equations
- Turing.TM2to1.trStmts₁ (Turing.TM2.Stmt.push k f q) = {Turing.TM2to1.Λ'.go k (Turing.TM2to1.StAct.push f) q, Turing.TM2to1.Λ'.ret q} ∪ Turing.TM2to1.trStmts₁ q
- Turing.TM2to1.trStmts₁ (Turing.TM2.Stmt.peek k f q) = {Turing.TM2to1.Λ'.go k (Turing.TM2to1.StAct.peek f) q, Turing.TM2to1.Λ'.ret q} ∪ Turing.TM2to1.trStmts₁ q
- Turing.TM2to1.trStmts₁ (Turing.TM2.Stmt.pop k f q) = {Turing.TM2to1.Λ'.go k (Turing.TM2to1.StAct.pop f) q, Turing.TM2to1.Λ'.ret q} ∪ Turing.TM2to1.trStmts₁ q
- Turing.TM2to1.trStmts₁ (Turing.TM2.Stmt.load a q) = Turing.TM2to1.trStmts₁ q
- Turing.TM2to1.trStmts₁ (Turing.TM2.Stmt.branch a q₁ q₂) = Turing.TM2to1.trStmts₁ q₁ ∪ Turing.TM2to1.trStmts₁ q₂
- Turing.TM2to1.trStmts₁ x✝ = ∅
Instances For
The TM2 emulator machine states written as a TM1 program.
This handles the go
and ret
states, which shuttle to and from a stack top.
Equations
- One or more equations did not get rendered due to their size.
- Turing.TM2to1.tr M (Turing.TM2to1.Λ'.normal q) = Turing.TM2to1.trNormal (M q)
Instances For
The relation between TM2 configurations and TM1 configurations of the TM2 emulator.
- mk {K : Type u_1} {Γ : K → Type u_2} {Λ : Type u_3} {σ : Type u_4} {q : Option Λ} {v : σ} {S : (k : K) → List (Γ k)} (L : ListBlank ((k : K) → Option (Γ k))) : (∀ (k : K), ListBlank.map (proj k) L = ListBlank.mk (List.map some (S k)).reverse) → TrCfg { l := q, var := v, stk := S } { l := Option.map Λ'.normal q, var := v, Tape := Tape.mk' ∅ (addBottom L) }
Instances For
The support of a set of TM2 states in the TM2 emulator.
Equations
- Turing.TM2to1.trSupp M S = S.biUnion fun (l : Λ) => insert (Turing.TM2to1.Λ'.normal l) (Turing.TM2to1.trStmts₁ (M l))