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.Code
program
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 a0
to the input. That is,zero' v = 0 :: v
.succ
returns the successor of the head of the input, defaulting to zero if there is no head:tail
returns the tail of the inputcons f fs
callsf
andfs
on the input and conses the results:comp f g
callsf
on the output ofg
:comp f g v = f (g v)
case f g
cases on the head of the input, callingf
org
depending on whether it is zero or a successor (similar toNat.casesOn
).fix f
callsf
repeatedly, using the head of the result off
to decide whether to callf
again 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
- Turing.ToPartrec.instInhabitedCode = { default := Turing.ToPartrec.Code.zero' }
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 a0
to the input. That is,zero' v = 0 :: v
.succ
returns the successor of the head of the input, defaulting to zero if there is no head:tail
returns the tail of the inputcons f fs
callsf
andfs
on the input and conses the results:comp f g
callsf
on the output ofg
:comp f g v = f (g v)
case f g
cases on the head of the input, callingf
org
depending on whether it is zero or a successor (similar toNat.casesOn
).fix f
callsf
repeatedly, using the head of the result off
to decide whether to callf
again 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)
, wherek
is 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 ofns
here 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 off
in afix f
expression: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 continuationk
with resultv
; that is,k v
wherek
is 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
- Turing.ToPartrec.instInhabitedCont = { default := Turing.ToPartrec.Cont.halt }
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
- Turing.ToPartrec.instInhabitedCfg = { default := Turing.ToPartrec.Cfg.halt default }
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 :: v
evaluates 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.tail
evaluates immediately, so we return it to the parent continuationcons f fs v = (f v).headI :: fs v
requires two sub-evaluations, so we evaluatef v
in the continuationk (_.headI :: fs v)
(calledCont.cons₁ fs v k
)comp f g v = f (g v)
requires two sub-evaluations, so we evaluateg v
in 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 v
org (n :: v.tail)
.fix f v = let v' := f v; if v'.headI = 0 then k v'.tail else fix f v'.tail
needs 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 v
stateCont.cons₁ fs as k v = k (v.headI :: fs as)
, so we evaluatefs as
now 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 v
withk
as the continuation.Cont.fix f k v = k (if v.headI = 0 then k v.tail else fix f v.tail)
, wherev
is a value, so we evaluate the if statement and either callk
withv.tail
, or callfix f v
withk
as the continuation (which immediately callsf
withCont.fix f k
as 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.