Modelling partial recursive functions using Turing machines #
The files TMConfig and TMToPartrec define a simplified basis for partial recursive functions,
and a Turing.TM2 model
Turing machine for evaluating these functions. This amounts to a constructive proof that every
Partrec function can be evaluated by a Turing machine.
Main definitions #
ToPartrec.Code: a simplified basis for partial recursive functions, valued inList ℕ →. List ℕ.ToPartrec.Code.eval: semantics for aToPartrec.Codeprogram
A simplified basis for partrec #
This section constructs the type Code, which is a data type of programs with List ℕ input and
output, with enough expressivity to write any partial recursive function. The primitives are:
zero'appends a0to the input. That is,zero' v = 0 :: v.succreturns the successor of the head of the input, defaulting to zero if there is no head:tailreturns the tail of the inputcons f fscallsfandfson the input and conses the results:comp f gcallsfon the output ofg:comp f g v = f (g v)
case f gcases on the head of the input, callingforgdepending on whether it is zero or a successor (similar toNat.casesOn).fix fcallsfrepeatedly, using the head of the result offto decide whether to callfagain or finish:
This basis is convenient because it is closer to the Turing machine model - the key operations are
splitting and merging of lists of unknown length, while the messy n-ary composition operation
from the traditional basis for partial recursive functions is absent - but it retains a
compositional semantics. The first step in transitioning to Turing machines is to make a sequential
evaluator for this basis, which we take up in the next section.
The type of codes for primitive recursive functions. Unlike Nat.Partrec.Code, this uses a set
of operations on List ℕ. See Code.eval for a description of the behavior of the primitives.
- zero' : Code
- succ : Code
- tail : Code
- cons : Code → Code → Code
- comp : Code → Code → Code
- case : Code → Code → Code
- fix : Code → Code
Instances For
Equations
- One or more equations did not get rendered due to their size.
- Turing.ToPartrec.instDecidableEqCode.decEq Turing.ToPartrec.Code.zero' Turing.ToPartrec.Code.zero' = isTrue ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq Turing.ToPartrec.Code.zero' Turing.ToPartrec.Code.succ = isFalse Turing.ToPartrec.instDecidableEqCode.decEq._proof_1
- Turing.ToPartrec.instDecidableEqCode.decEq Turing.ToPartrec.Code.zero' Turing.ToPartrec.Code.tail = isFalse Turing.ToPartrec.instDecidableEqCode.decEq._proof_2
- Turing.ToPartrec.instDecidableEqCode.decEq Turing.ToPartrec.Code.zero' (a.cons a_1) = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq Turing.ToPartrec.Code.zero' (a.comp a_1) = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq Turing.ToPartrec.Code.zero' (a.case a_1) = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq Turing.ToPartrec.Code.zero' a.fix = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq Turing.ToPartrec.Code.succ Turing.ToPartrec.Code.zero' = isFalse Turing.ToPartrec.instDecidableEqCode.decEq._proof_7
- Turing.ToPartrec.instDecidableEqCode.decEq Turing.ToPartrec.Code.succ Turing.ToPartrec.Code.succ = isTrue ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq Turing.ToPartrec.Code.succ Turing.ToPartrec.Code.tail = isFalse Turing.ToPartrec.instDecidableEqCode.decEq._proof_8
- Turing.ToPartrec.instDecidableEqCode.decEq Turing.ToPartrec.Code.succ (a.cons a_1) = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq Turing.ToPartrec.Code.succ (a.comp a_1) = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq Turing.ToPartrec.Code.succ (a.case a_1) = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq Turing.ToPartrec.Code.succ a.fix = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq Turing.ToPartrec.Code.tail Turing.ToPartrec.Code.zero' = isFalse Turing.ToPartrec.instDecidableEqCode.decEq._proof_13
- Turing.ToPartrec.instDecidableEqCode.decEq Turing.ToPartrec.Code.tail Turing.ToPartrec.Code.succ = isFalse Turing.ToPartrec.instDecidableEqCode.decEq._proof_14
- Turing.ToPartrec.instDecidableEqCode.decEq Turing.ToPartrec.Code.tail Turing.ToPartrec.Code.tail = isTrue ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq Turing.ToPartrec.Code.tail (a.cons a_1) = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq Turing.ToPartrec.Code.tail (a.comp a_1) = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq Turing.ToPartrec.Code.tail (a.case a_1) = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq Turing.ToPartrec.Code.tail a.fix = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq (a.cons a_1) Turing.ToPartrec.Code.zero' = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq (a.cons a_1) Turing.ToPartrec.Code.succ = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq (a.cons a_1) Turing.ToPartrec.Code.tail = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq (a.cons a_1) (a_2.comp a_3) = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq (a.cons a_1) (a_2.case a_3) = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq (a.cons a_1) a_2.fix = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq (a.comp a_1) Turing.ToPartrec.Code.zero' = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq (a.comp a_1) Turing.ToPartrec.Code.succ = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq (a.comp a_1) Turing.ToPartrec.Code.tail = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq (a.comp a_1) (a_2.cons a_3) = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq (a.comp a_1) (a_2.case a_3) = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq (a.comp a_1) a_2.fix = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq (a.case a_1) Turing.ToPartrec.Code.zero' = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq (a.case a_1) Turing.ToPartrec.Code.succ = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq (a.case a_1) Turing.ToPartrec.Code.tail = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq (a.case a_1) (a_2.cons a_3) = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq (a.case a_1) (a_2.comp a_3) = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq (a.case a_1) a_2.fix = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq a.fix Turing.ToPartrec.Code.zero' = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq a.fix Turing.ToPartrec.Code.succ = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq a.fix Turing.ToPartrec.Code.tail = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq a.fix (a_1.cons a_2) = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq a.fix (a_1.comp a_2) = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq a.fix (a_1.case a_2) = isFalse ⋯
- Turing.ToPartrec.instDecidableEqCode.decEq a.fix b.fix = if h : a = b then h ▸ have inst := Turing.ToPartrec.instDecidableEqCode.decEq a a; isTrue ⋯ else isFalse ⋯
Instances For
Equations
The semantics of the Code primitives, as partial functions List ℕ →. List ℕ. By convention
we functions that return a single result return a singleton [n], or in some cases n :: v where
v will be ignored by a subsequent function.
zero'appends a0to the input. That is,zero' v = 0 :: v.succreturns the successor of the head of the input, defaulting to zero if there is no head:tailreturns the tail of the inputcons f fscallsfandfson the input and conses the results:comp f gcallsfon the output ofg:comp f g v = f (g v)
case f gcases on the head of the input, callingforgdepending on whether it is zero or a successor (similar toNat.casesOn).fix fcallsfrepeatedly, using the head of the result offto decide whether to callfagain or finish:
Equations
- Turing.ToPartrec.Code.zero'.eval = fun (v : List ℕ) => pure (0 :: v)
- Turing.ToPartrec.Code.succ.eval = fun (v : List ℕ) => pure [v.headI.succ]
- Turing.ToPartrec.Code.tail.eval = fun (v : List ℕ) => pure v.tail
- (f.cons fs).eval = fun (v : List ℕ) => do let n ← f.eval v let ns ← fs.eval v pure (n.headI :: ns)
- (f.comp g).eval = fun (v : List ℕ) => g.eval v >>= f.eval
- (f.case g).eval = fun (v : List ℕ) => Nat.rec (f.eval v.tail) (fun (y : ℕ) (x : Part (List ℕ)) => g.eval (y :: v.tail)) v.headI
- f.fix.eval = PFun.fix fun (v : List ℕ) => Part.map (fun (v : List ℕ) => if v.headI = 0 then Sum.inl v.tail else Sum.inr v.tail) (f.eval v)
Instances For
rfind f performs the function of the rfind primitive of partial recursive functions.
rfind f v returns the smallest n such that (f (n :: v)).head = 0.
It is implemented as:
rfind f v = pred (fix (fun (n::v) => f (n::v) :: n+1 :: v) (0 :: v))
The idea is that the initial state is 0 :: v, and the fix keeps n :: v as its internal state;
it calls f (n :: v) as the exit test and n+1 :: v as the next state. At the end we get
n+1 :: v where n is the desired output, and pred (n+1 :: v) = [n] returns the result.
Equations
Instances For
prec f g implements the prec (primitive recursion) operation of partial recursive
functions. prec f g evaluates as:
prec f g [] = [f []]prec f g (0 :: v) = [f v]prec f g (n+1 :: v) = [g (n :: prec f g (n :: v) :: v)]
It is implemented as:
G (a :: b :: IH :: v) = (b :: a+1 :: b-1 :: g (a :: IH :: v) :: v)
F (0 :: f_v :: v) = (f_v :: v)
F (n+1 :: f_v :: v) = (fix G (0 :: n :: f_v :: v)).tail.tail
prec f g (a :: v) = [(F (a :: f v :: v)).head]
Because fix always evaluates its body at least once, we must special case the 0 case to avoid
calling g more times than necessary (which could be bad if g diverges). If the input is
0 :: v, then F (0 :: f v :: v) = (f v :: v) so we return [f v]. If the input is n+1 :: v,
we evaluate the function from the bottom up, with initial state 0 :: n :: f v :: v. The first
number counts up, providing arguments for the applications to g, while the second number counts
down, providing the exit condition (this is the initial b in the return value of G, which is
stripped by fix). After the fix is complete, the final state is n :: 0 :: res :: v where
res is the desired result, and the rest reduces this to [res].
Equations
- One or more equations did not get rendered due to their size.
Instances For
From compositional semantics to sequential semantics #
Our initial sequential model is designed to be as similar as possible to the compositional
semantics in terms of its primitives, but it is a sequential semantics, meaning that rather than
defining an eval c : List ℕ →. List ℕ function for each program, defined by recursion on
programs, we have a type Cfg with a step function step : Cfg → Option cfg that provides a
deterministic evaluation order. In order to do this, we introduce the notion of a continuation,
which can be viewed as a Code with a hole in it where evaluation is currently taking place.
Continuations can be assigned a List ℕ →. List ℕ semantics as well, with the interpretation
being that given a List ℕ result returned from the code in the hole, the remainder of the
program will evaluate to a List ℕ final value.
The continuations are:
halt: the empty continuation: the hole is the whole program, whatever is returned is the final result. In our notation this is just_.cons₁ fs v k: evaluating the first part of acons, that isk (_ :: fs v), wherekis the outer continuation.cons₂ ns k: evaluating the second part of acons:k (ns.headI :: _). (Technically we don't need to hold on to all ofnshere since we are already committed to taking the head, but this is more regular.)comp f k: evaluating the first part of a composition:k (f _).fix f k: waiting for the result offin afix fexpression:k (if _.headI = 0 then _.tail else fix f (_.tail))
The type Cfg of evaluation states is:
ret k v: we have received a result, and are now evaluating the continuationkwith resultv; that is,k vwherekis ready to evaluate.halt v: we are done and the result isv.
The main theorem of this section is that for each code c, the state stepNormal c halt v steps
to v' in finitely many steps if and only if Code.eval c v = some v'.
Equations
The semantics of a continuation.
Equations
- Turing.ToPartrec.Cont.halt.eval = pure
- (Turing.ToPartrec.Cont.cons₁ fs as k).eval = fun (v : List ℕ) => do let ns ← fs.eval as k.eval (v.headI :: ns)
- (Turing.ToPartrec.Cont.cons₂ ns k).eval = fun (v : List ℕ) => k.eval (ns.headI :: v)
- (Turing.ToPartrec.Cont.comp f k).eval = fun (v : List ℕ) => f.eval v >>= k.eval
- (Turing.ToPartrec.Cont.fix f k).eval = fun (v : List ℕ) => if v.headI = 0 then k.eval v.tail else f.fix.eval v.tail >>= k.eval
Instances For
The set of configurations of the machine:
halt v: The machine is about to stop andv : List ℕis the result.ret k v: The machine is about to passv : List ℕto continuationk : Cont.
We don't have a state corresponding to normal evaluation because these are evaluated immediately
to a ret "in zero steps" using the stepNormal function.
Instances For
Equations
Instances For
Evaluating c : Code in a continuation k : Cont and input v : List ℕ. This goes by
recursion on c, building an augmented continuation and a value to pass to it.
zero' v = 0 :: vevaluates immediately, so we return it to the parent continuationsucc v = [v.headI.succ]evaluates immediately, so we return it to the parent continuationtail v = v.tailevaluates immediately, so we return it to the parent continuationcons f fs v = (f v).headI :: fs vrequires two sub-evaluations, so we evaluatef vin the continuationk (_.headI :: fs v)(calledCont.cons₁ fs v k)comp f g v = f (g v)requires two sub-evaluations, so we evaluateg vin the continuationk (f _)(calledCont.comp f k)case f g v = v.head.casesOn (f v.tail) (fun n => g (n :: v.tail))has the information needed to evaluate the case statement, so we do that and transition to eitherf vorg (n :: v.tail).fix f v = let v' := f v; if v'.headI = 0 then k v'.tail else fix f v'.tailneeds to first evaluatef v, so we do that and leave the rest for the continuation (calledCont.fix f k)
Equations
- One or more equations did not get rendered due to their size.
- Turing.ToPartrec.stepNormal Turing.ToPartrec.Code.zero' = fun (k : Turing.ToPartrec.Cont) (v : List ℕ) => Turing.ToPartrec.Cfg.ret k (0 :: v)
- Turing.ToPartrec.stepNormal Turing.ToPartrec.Code.succ = fun (k : Turing.ToPartrec.Cont) (v : List ℕ) => Turing.ToPartrec.Cfg.ret k [v.headI.succ]
- Turing.ToPartrec.stepNormal Turing.ToPartrec.Code.tail = fun (k : Turing.ToPartrec.Cont) (v : List ℕ) => Turing.ToPartrec.Cfg.ret k v.tail
- Turing.ToPartrec.stepNormal (f.cons fs) = fun (k : Turing.ToPartrec.Cont) (v : List ℕ) => Turing.ToPartrec.stepNormal f (Turing.ToPartrec.Cont.cons₁ fs v k) v
- Turing.ToPartrec.stepNormal (f.comp g) = fun (k : Turing.ToPartrec.Cont) (v : List ℕ) => Turing.ToPartrec.stepNormal g (Turing.ToPartrec.Cont.comp f k) v
- Turing.ToPartrec.stepNormal f.fix = fun (k : Turing.ToPartrec.Cont) (v : List ℕ) => Turing.ToPartrec.stepNormal f (Turing.ToPartrec.Cont.fix f k) v
Instances For
Evaluating a continuation k : Cont on input v : List ℕ. This is the second part of
evaluation, when we receive results from continuations built by stepNormal.
Cont.halt v = v, so we are done and transition to theCfg.halt vstateCont.cons₁ fs as k v = k (v.headI :: fs as), so we evaluatefs asnow with the continuationk (v.headI :: _)(calledcons₂ v k).Cont.cons₂ ns k v = k (ns.headI :: v), where we now have everything we need to evaluatens.headI :: v, so we return it tok.Cont.comp f k v = k (f v), so we callf vwithkas the continuation.Cont.fix f k v = k (if v.headI = 0 then k v.tail else fix f v.tail), wherevis a value, so we evaluate the if statement and either callkwithv.tail, or callfix f vwithkas the continuation (which immediately callsfwithCont.fix f kas the continuation).
Equations
- Turing.ToPartrec.stepRet Turing.ToPartrec.Cont.halt x✝ = Turing.ToPartrec.Cfg.halt x✝
- Turing.ToPartrec.stepRet (Turing.ToPartrec.Cont.cons₁ fs as k) x✝ = Turing.ToPartrec.stepNormal fs (Turing.ToPartrec.Cont.cons₂ x✝ k) as
- Turing.ToPartrec.stepRet (Turing.ToPartrec.Cont.cons₂ ns k) x✝ = Turing.ToPartrec.stepRet k (ns.headI :: x✝)
- Turing.ToPartrec.stepRet (Turing.ToPartrec.Cont.comp f k) x✝ = Turing.ToPartrec.stepNormal f k x✝
- Turing.ToPartrec.stepRet (Turing.ToPartrec.Cont.fix f k) x✝ = if x✝.headI = 0 then Turing.ToPartrec.stepRet k x✝.tail else Turing.ToPartrec.stepNormal f (Turing.ToPartrec.Cont.fix f k) x✝.tail
Instances For
In order to extract a compositional semantics from the sequential execution behavior of
configurations, we observe that continuations have a monoid structure, with Cont.halt as the unit
and Cont.then as the multiplication. Cont.then k₁ k₂ runs k₁ until it halts, and then takes
the result of k₁ and passes it to k₂.
We will not prove it is associative (although it is), but we are instead interested in the
associativity law k₂ (eval c k₁) = eval c (k₁.then k₂). This holds at both the sequential and
compositional levels, and allows us to express running a machine without the ambient continuation
and relate it to the original machine's evaluation steps. In the literature this is usually
where one uses Turing machines embedded inside other Turing machines, but this approach allows us
to avoid changing the ambient type Cfg in the middle of the recursion.
Equations
- Turing.ToPartrec.Cont.halt.then = fun (k' : Turing.ToPartrec.Cont) => k'
- (Turing.ToPartrec.Cont.cons₁ fs as k).then = fun (k' : Turing.ToPartrec.Cont) => Turing.ToPartrec.Cont.cons₁ fs as (k.then k')
- (Turing.ToPartrec.Cont.cons₂ ns k).then = fun (k' : Turing.ToPartrec.Cont) => Turing.ToPartrec.Cont.cons₂ ns (k.then k')
- (Turing.ToPartrec.Cont.comp f k).then = fun (k' : Turing.ToPartrec.Cont) => Turing.ToPartrec.Cont.comp f (k.then k')
- (Turing.ToPartrec.Cont.fix f k).then = fun (k' : Turing.ToPartrec.Cont) => Turing.ToPartrec.Cont.fix f (k.then k')
Instances For
The then k function is a "configuration homomorphism". Its operation on states is to append
k to the continuation of a Cfg.ret state, and to run k on v if we are in the Cfg.halt v
state.
Equations
- (Turing.ToPartrec.Cfg.halt a).then = fun (k' : Turing.ToPartrec.Cont) => Turing.ToPartrec.stepRet k' a
- (Turing.ToPartrec.Cfg.ret k v).then = fun (k' : Turing.ToPartrec.Cont) => Turing.ToPartrec.Cfg.ret (k.then k') v
Instances For
The stepNormal function respects the then k' homomorphism. Note that this is an exact
equality, not a simulation; the original and embedded machines move in lock-step until the
embedded machine reaches the halt state.
This is a temporary definition, because we will prove in code_is_ok that it always holds.
It asserts that c is semantically correct; that is, for any k and v,
eval (stepNormal c k v) = eval (Cfg.ret k (Code.eval c v)), as an equality of partial values
(so one diverges iff the other does).
In particular, we can let k = Cont.halt, and then this asserts that stepNormal c Cont.halt v
evaluates to Cfg.halt (Code.eval c v).
Equations
- One or more equations did not get rendered due to their size.