Lectures onType Theory
Chapter 55
Chapter 55Optional

Temporal Types and Functional Reactive Programming

Prerequisites. Direct starred prerequisites: none. No later core chapter depends on this route.

Two defects hide behind the innocent type StreamAStreamB. A function may inspect tomorrow’s input to produce today’s output, violating causality. A causal definition may also retain every old input behind delayed closures, producing an implicit space leak. Ordinary function types express neither prohibition. Simply RaTT separates stable data, the present instant, and the next instant in the typing context, then gives those restrictions an abstract machine [BGM19].

The leak and the lookahead

Write a stream informally as a0,a1,a2,. The specification yn=xn+xn+1 is productive as a mathematical stream equation, but it is not causal: the first output depends on an input not yet received. By contrast, running sum is causal: y0=x0,yn+1=yn+xn+1. Yet an implementation that keeps the entire prefix [x0,,xn] to compute yn consumes unbounded space for no semantic reason. Causality and bounded reactive memory are separate obligations.

Jeffrey’s LTL presentation gives temporal propositions computational content: next and always types delimit when a reactive value is available [Jef12]. It establishes the temporal discipline needed to reject lookahead. It does not, by that fact alone, forbid a delayed closure from retaining an old heap. Simply RaTT’s contribution is the combination of a Fitch discipline and a machine that discards the old heap at each stream step.

The exact comparison is semantic. If A[s,u] means that the reactive proposition A holds throughout the interval from s through u, Jeffrey’s non-strict constrains type is interpreted by Con(A,B)(s)=us(A[s,u]B(u)). An inhabitant can compute the output at u from the input history only through u, which is the causal-function interface. Because the reactive types of Figures 3–5 are the direct interval semantics of LTL, a closed inhabitant of the interpretation of a formula F makes F valid, under the paper’s stated assumption that the ambient dependent type theory is sound for classical logic. This is temporal soundness, not a storage theorem.

Exercise 55.1

★☆☆ Give a causal stream computation that retains its history. Give a productive noncausal specification, and name both defects.

Fitch contexts and temporal types

Types include ordinary sums, products, naturals, functions, guarded recursive types, the later type A, and the stable modality A. Contexts are ordered lists containing assumptions and two tokens. The later modality follows Nakano’s guarded-recursion idea; the Fitch rules and metatheorems below are those of Simply RaTT, not theorem transfers from Nakano’s calculus [Nak00]: Γ::=Γ,x:AΓ,Γ,. There is at most one lock and one tick; a tick may occur only to the right of a lock. A suffix is token-free when it contains neither token, and a context is tick-free when it contains no tick. The variable and function rules reveal why order matters:

tokenFree(Γ)
Γ,x:A,Γx:A
RaTT-Var
Γ,x:At:BtickFree(Γ)
Γλx.t:AB
RaTT-Abs
Γt:ABΓu:A
Γtu:B
RaTT-App

Rule RaTT-Var makes assumptions to the left of a temporal token inaccessible. Rule RaTT-Abs prevents a function closure from capturing a current-time assumption beneath a tick.

The modal rules are

Γ,t:A
Γdelayt:A
RaTT-Delay
Γt:AΓ,,Γctx
Γ,,Γadvt:A
RaTT-Adv
Γ,t:A
Γboxt:A
RaTT-Box
Γt:AtokenFree(Γ)
Γ,,Γunboxt:A
RaTT-Unbox

Delay closes a term checked one tick later. Advance opens such a term only after that tick has arrived. Boxing moves code behind the lock; unboxing may cross a lock but not another temporal token in its suffix.

A type is stable when it is generated by 1,Nat,A,A×B,A+B with stable components. Function and later types are deliberately absent. Boxed types may nevertheless enclose an arbitrary type, because their terms are checked behind the lock. Stable values may be carried across a tick by

Γt:AAstableΓ,,Γctx
Γ,,Γprogresst:A
RaTT-Progress
Γt:AAstableΓ,,Γctx
Γ,,Γpromotet:A
RaTT-Promote

Progress carries stable data across a tick; promote makes stable initial data available to the right of the lock. Neither rule permits an arbitrary closure containing the current heap.

Exercise 55.2

★★☆ Assume x:A occurs immediately to the left of . Attempt to type λz.x after the tick. Name the failed premise. Repeat when x has stable type and is moved with progress.

Guarded streams and reactive programs

Guarded recursive types have explicit fold and unfold operations. Define StrA:=μα.A×α. The recursive occurrence is below later. A stream cell is therefore available now, while its tail becomes available after one tick. The fixed point rule Γ,,x:At:AΓfixx.t:ARaTTFix gives recursive code only a delayed self-reference.

For a closed stable function f:(AB), stream mapping has type map:(AB)(StrAStrB),mapf=fixm.λs.let(a,s)=outsininto((unboxf)a,delay((advm)(advs))). The two advances occur together after the same tick: both the recursive program and the input tail are then available. There is no derivation for a version that advances the input tail before producing the head.

Running sum carries a stable natural accumulator. Its recursive worker has type sum:(NatStrNatStrNat),sum=fixf.λn.λs.let(x,s)=outsininto(n+x,delay((advf)(progress(n+x))(advs))). Inside the delay, the recursive code and tail advance together, while the new accumulator crosses the tick by RaTT-Progress. The input cell does not cross it.

The source library makes switching explicit. Define events by EvAevA+(EvA), with constructors val and wait. Then switch:(StrAEv(StrA)StrA) is defined as fixsw.λs.λe.. Its two body branches are bodysw(x::xs)(waitfas)=x::delay((advsw)(advxs)(advfas)),bodyswxs(valys)=ys. Before an event arrives, the current head is emitted and every tail advances under one delay. A present event may replace the stream immediately; no future event is inspected to choose the current head.

Sampling is a flow operation, not deletion of ticks. Let Clock:=StrBool,FlowA:=Str(MaybeA). The source’s sampler first constructs a slower clock everyNth:Nat(ClockClock) by carrying a stable counter, and then masks a flow by when:(ClockFlowAFlowA). Inside when=fixw.λc.λa., the body is bodyw(c::cs)(a::as)=(if c then a else nothing)::delay((advw)(advcs)(advas)). Thus a slow clock produces nothing on skipped global ticks; the result remains a productive stream.

Accumulation is the stable-state combinator scan:Bstable(BAB)(BStrAStrB). Its current cell is b=fba, and its delayed call advances the input tail while carrying b by RaTT-Progress. Running sum is scan(box(+))0. A complete network is sumfromMaybe(box0)whenbasicClockmap(boxjust). It maps each input to a present flow cell, samples on the basic clock, fills no holes, and accumulates. On (2,11,5,) its first three outputs are (2,13,18). Replacing basicClock by everyNth 2 basicClock changes which cells are present, but not the one-global-tick cadence. Switch, sampling, and scan all prohibit an ordinary closure over the present heap from entering a delayed tail.

Exercise 55.3

★★☆ Annotate the map body with the positions of and . Explain why advancing m but not the input tail cannot construct the next stream cell.

The two-heap abstract machine

A store is one of ,ηL,ηNηL. The later heap ηL receives delayed computations. After a stream step, it becomes the new now heap ηN; the old now heap is discarded. A delay allocates a fresh location in ηL. An advance retrieves a computation from ηN. The tokens in the typing context mirror exactly which heap operations are available.

A closed stream state is a pair of a store and a term. We write (η,t)s(v,η,t) when evaluation exposes a head v, makes the later heap the new now heap, and continues at t. A transducer step records an input and an output: (η,t)tv/v(η,t). For the running-sum transducer, the source trace is runSum(2,11,5)=(2,13,18). After each transition, one natural accumulator and the delayed tail remain; the machine does not retain the consumed input cell.

Proposition 55.1 — No old-heap reachability

Use the source’s machine-state typing and heap-indexed logical relation. If a related stream or transducer state takes one machine step, then every location reachable from the related continuation belongs to the new now heap or the new later heap, not to the discarded old now heap.

Proof of Proposition 55.1 — No old-heap reachability

Proof. This is the garbage-collection consequence of the source’s fundamental lemma, not a theorem of the four surface rules alone. The logical relation indexes values and computations by the machine world. Its A clause places delayed computations in the later component; the successor-world clause moves that component to the new now heap and forgets the old now component. The stable-type lemma says that a related stable value is independent of temporal heap locations. Applying the fundamental lemma to the step therefore leaves no related continuation root in the forgotten component. The world and term relations used in this argument are frozen in section 55.5; the full store-typing and machine induction remain source obligations [BGM19]. ◻

The proposition excludes implicit leaks caused by the temporal machinery. It does not bound explicit stable data. If the language is extended with lists, stable whenever their elements are stable, a program may intentionally append every input to such a list. That list grows, and the extended type system permits it.

Exercise 55.4

★★☆ Modify running sum so that its stable state is a list of all previous inputs. Explain why proposition 55.1 still holds although the program uses unbounded space.

The step-indexed logical relation

A world records a store shape and a finite sequence of future heap fragments. Write www when w extends the current allocations without changing locations already present. For a semantic environment σ, define three relations: vV[[A]]σH,tT[[A]]σH,γC[[Γ]]σH. The value relation follows type structure. At A, a location must denote a term in T[[A]] at the next index. At A, the value must satisfy V[[A]] at every admissible future world. The term relation requires evaluation to a related value without illegal heap access. The context relation maps each accessible variable to a related value and interprets locks and ticks by the corresponding world transition. The definition is well founded by lexicographic induction on the remaining time index, type size, and the value/term tag.

Theorem 55.2 — Fundamental Property, Simply RaTT Theorem 6.3

If Γt:A and γC[[Γ]]σH, then tγT[[A]]σH.

Proof of Theorem 55.2 — Fundamental Property, Simply RaTT Theorem 6.3

Proof. Induct on the typing derivation. Variables follow from the context relation; products, sums, naturals, abstraction, and application follow from their value clauses and the induction hypotheses. For delay, allocate the premise term in the later heap. Its ticked premise is interpreted at the successor index, so the stored term lies in the later-type clause. For advance, the context’s tick identifies the now heap; retrieve the related stored term and apply its term clause. For box, the lock removes dependence on the current temporal world, so the induction hypothesis holds at every future extension. For unbox, instantiate that universal clause at the current future world. Progress and promote use stability to preserve the value relation across a tick and a lock, respectively. In the fixed-point case, induction on the time index justifies the delayed recursive hypothesis. Fold and unfold use the guarded recursive-type clause. These cases exhaust the typing rules. ◻

The theorem connects syntax to the machine. Productivity and causality are not separate preservation/progress theorems pasted onto the calculus; they are consequences of the stream and transducer instances of the relation.

Theorem 55.3 — Productivity, Simply RaTT Theorem 3.1

Let A be a value type built from 1, naturals, sums, and products. If t:StrA, then for every n the abstract machine takes n stream steps and produces values v1,,vn, each typed at A.

Proof of Theorem 55.3 — Productivity, Simply RaTT Theorem 3.1

Proof. Unbox t to obtain a state in the stream relation at index n. Apply theorem 55.2 to the empty substitution. The stream clause exposes one related head and a tail at index n1. Iterate this argument n times. The value-type restriction converts semantic membership of each head into ordinary typing at A. ◻

Theorem 55.4 — Causality, Simply RaTT Theorem 3.2

Let A and B be value types. A closed term t:(StrAStrB) has an initial state in the source’s transducer relation. If a state lies in its (k+1)-step approximation and receives a typed input v:A, it takes one transducer step, emits a typed output v:B, and its successor lies in the k-step approximation.

Proof of Theorem 55.4 — Causality, Simply RaTT Theorem 3.2

Proof. Apply the Fundamental Property to the unboxed function and a related input stream whose head is v. The function and stream clauses yield one output cell before the input tail becomes available. The emitted head belongs to V[[B]], hence is typed at the value type B. The delayed tails move to the successor world, which lowers the approximation index from k+1 to k. No premise supplies a future input cell to the current output, which is the causal dependency claim. ◻

Exercise 55.5

★★★ Identify the different hypotheses in theorem 55.3, theorem 55.4. Explain why productivity of a closed output stream alone does not prove causality of a transducer.

Comparison and boundary

System Static distinction Established conclusion
Jeffrey’s LTL FRP temporal propositions as types temporal availability and causal reactive structure
Simply RaTT lock, tick, stable data, two heaps source Theorems 3.1, 3.2, 6.3 and absence of implicit machine leaks
Coeffect system of Chapter 53 contextual requirements no Simply RaTT machine theorem without a translation

Source-gated temporal resources and flow refinements

Ahman and Žajdela’s λ[τ] is a separate fine-grain call-by-value calculus, not an extension theorem for Simply RaTT [A Z24]. Natural-number grades measure time: τN,X,Y::=b1X×YXY!τ[τ]X,Γ::=Γ,x:XΓ,τ. A value of [τ]X is usable only after at least τ units; a computation of type X!τ returns an X after spending τ units. The load-bearing rules are

ΓM:X!τΓ,τ,x:XN:Y!τ
Γlet x=M in N:Y!(τ+τ)
TR-Let
Γ,τM:X!τ
Γdelay τ M:X!(τ+τ)
TR-Delay
Γ,τV:XΓ,x:[τ]XN:Y!τ
Γbox[τ]V as x in N:Y!τ
TR-Box
ττΓΓτV:[τ]XΓ,x:XN:Y!τ
Γunbox[τ]V as x in N:Y!τ
TR-Unbox

Here τΓ sums the time markers in Γ, and Γτ moves back through that accumulated time. Thus TR-Unbox checks both that enough time has elapsed and that the boxed value was already in scope at the earlier point.

A machine state stores elapsed-time markers and bindings x[τ]XV. Its three temporal steps are Sdelay τ MtrS,τM,Sbox[τ]V as x in NtrS,x[τ]XVN,Sunbox[τ]y as x in NtrSN[S(y)/x](yS). If ΓS is the context represented by S, the paper proves:

  • if S and ΓSM:X!τ, then M is a return or unhandled-operation result, or the machine steps (Theorem 3.7);

  • if that state steps to SM, then S and some τ satisfies τS+τ=τS+τ and ΓSM:X!τ (Theorem 3.10);

  • translating states to temporal computation contexts KS makes each step equationally sound: KS[M]KS[M]:X!(τS+τ) (Corollary 4.10).

The accompanying Agda development contains the syntax, renaming, substitution, progress, preservation, and equational theory. The authors explicitly report that the proof of Theorem 4.9, from which the corollary is obtained, was not yet formalized. The paper proof and the partial formalization must therefore remain separate evidence.

Likewise, the FlowA=Str(MaybeA) construction above is a Simply RaTT library encoding, not a primitive clock-indexed flow type. A refinement such as FlowcA, with a proposed masking rule Γd:ClockdΓf:FlowcAΓwhen d f:FlowcdA, is a conjectural extension here. Before it can inherit productivity or the old-heap result, it needs clock subtyping, operational semantics, a clock-indexed logical relation, and a proof that masking preserves reachable roots. No such theorem is imported by the displayed rule. The parallel claim that arbitrary λ[τ] state can be added to Simply RaTT without implicit leaks is also a conjecture: the two state models require an explicit translation and invariant-preservation proof.

Sources and theorem boundary

The typing rules are Figure 4, the machine is Sections 3–4, productivity and causality are Theorems 3.1–3.2, and the Fundamental Property is Theorem 6.3 of Bahr, Graulund, and Møgelberg [BGM19]. The local Coq archive is pinned to the authors’ repository commit recorded in the reference package; archive integrity is not a claim of replay under current Rocq. Jeffrey supplies the LTL precursor comparison [Jef12]. Simply RaTT’s switch, scan, and Lustre-flow encodings are in Sections 4–5 of that paper. The temporal-resource typing rules are Figure 3, its machine is Figure 4, and its exact results are Theorems 3.7, 3.10, 4.9 and Corollary 4.10 of Ahman and Žajdela [A Z24]. Nakano’s modality is cited only as the guarded-recursion precursor [Nak00]. This chapter proves neither a generic progress and preservation package for every temporal calculus nor a bound on deliberate growth of stable application data.

Suggested first pass.

Do exercise 55.6, exercise 55.7 before the implementation problem.

Exercise 55.6

★★☆ Rejected lookahead. Formalize the stream equation yn=xn+xn+1 and locate the failed RaTT-Adv derivation for its first output.

Exercise 55.7

★★☆ Typed network. Compose map, sampling, and running sum. Draw one lock/tick boundary and calculate the first three outputs on (2,11,5,).

Exercise 55.8

★★★ Fundamental case. Write the complete world-index calculation for RaTT-Delay followed one instant later by RaTT-Adv.

Exercise 55.9

★★☆ Space audit. Compare a stable natural accumulator with a stable list accumulator. State which memory is discarded by the machine and which memory is retained explicitly.

Exercise 55.10

★★★ Practical project.simply-ratt-stream-checker The implemented algorithm is a finite running-sum transducer paired with a root-count model. Preserve two invariants: output i depends only on inputs through i, and the bounded model retains exactly one temporal root after each step. Run artifacts/ch55-simply-ratt-stream/corpus.kp; on the named input (2,11,5), require (2,13,18), current-input dependence, root counts (1,1,1), and detection of the history counts (1,2,3). Acceptance is the four named PASS lines and empty audit in appendix E. Then add a causal two-state transducer and its expected trace. Finally replace the bounded-root function by the history-retaining one and require the unchanged root-bound oracle to reject the mutant.

Search the book

Type to search the local edition.