Lectures onType Theory
Chapter 103
Chapter 103Optional

Dependent Effects and Call-by-Push-Value

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

Let read:FNat be the state-reading computation of chapter 22. An ordinary monadic bind can form readtoxinreturnx:FNat. It cannot assign the result the family F(VecBoolx), because x is not in scope in the type of the whole computation. Substituting the effectful term read into that family is worse: another read may produce another number. The defect is not missing notation. Dependency needs a stable value, whereas the operation has not yet produced one.

Three incompatible unrestricted principles

Definition 103.1 — Fire signature

Fix a universe U containing a Boolean type B, a unit type 1, and an empty type . There are closed terms true,false:B and unit:1. The universe is closed under dependent products and binary products: ΓA:UΓ,x:AB:UΓΠx:AB:U,ΓA:UΓB:UΓA×B:U. Products have pairing and projections with their beta equations; dependent products have lambda abstraction, application, and beta conversion. Write AB for Π_:AB, and write Γ:A when A is inhabited. No identity type, extensionality principle, or effect equation is assumed by the argument below.

Definition 103.2 — The three vertices

The theory has substitution when

Γ,x:A:BΓu:A
Γ:B[u/x]
Subst

It has dependent Boolean elimination when

Γ,x:BA:UΓ:A[true/x]Γ:A[false/x]
Γ,x:B:A
Dep- B-E

is admissible, and when the same case analysis is available one level up: from ΓAt:U and ΓAf:U one may form a family Γ,x:BA:U with A[true/x]At and A[false/x]Af. The second clause is what makes the rule an induction principle rather than a proof of two cases; the proof below uses it once, to build a predicate. It has an observable effect when there are a closed t:B and a one-hole context C[] such that C[true]true,C[false]true,C[t]false. This observability premise is stronger than merely having an operation. Printing, an unhandled exception, and divergence need not provide such a Boolean discriminator inside the theory.

Theorem 103.3 — Fire Triangle

Every theory extending the signature of definition 103.1 with the three vertices of definition 103.2 is inconsistent: :.

Proof of Theorem 103.3 — Fire Triangle

Proof. Write XY for the pair of functions (XY)×(YX), and define Leibniz equality at type A by a=Ab:=ΠP:AU.PaPb. Let t and C[] witness observability. Apply dependent Boolean elimination to the family C[x]=Btrue. Its two branches are inhabited because both C[true] and C[false] are judgmentally true. Thus x:B:C[x]=Btrue. Substitution at t gives :C[t]=Btrue. Conversion along C[t]false gives :false=Btrue.

It remains to turn that equality into an inhabitant of . The second clause of definition 103.2 builds the family x:BP:U determined by P[false/x],P[true/x]1, and this is the only step that needs case analysis at the universe. Instantiating the Leibniz equality at P gives :1; applying its second component to unit:1 produces an inhabitant of . ◻

In call by value, beta substitution is restricted to values, so the first vertex is weakened. In call by name, arbitrary substitution remains, but a dependent eliminator must force the scrutinee in its classifier as well as its term; unrestricted elimination is weakened. These are exact exits from the theorem, not evaluation-order slogans.

Exercise 103.1

★★☆ Delete the contextual equation C[t]false from definition 103.2. Locate the first conversion in the proof of theorem 103.3 that can no longer be derived. Explain why a diverging Boolean term need not restore that equation.

Values and computations

Call-by-push-value prevents an effectful computation from occupying a value variable. Chapter 22 fixes the nondependent calculus; this chapter states one delta against that signature and then proves at the enlarged one.

The delta has three parts. First, the type formers Σ and Π become dependent, so a value type may mention a value variable: A::=1BΣx:ABUB,B::=FAΠx:AB,V::=xunit(V,W)thunkM,M::=returnVforceVλx.MMVMtoxinNdivergeBμzBMwrites.Mreadtos(Ms)printm.Mchoosei(Mi)errorBe. Second, the seven operation forms are carried over unchanged from chapter 22. Their complete typing delta is

ΓcdivergeB:B
Diverge
Γ,z:UBcM:B
ΓcμzBM:B
Rec
ΓcerrorBe:B
Error
ΓcM:B
Γcwrites.M:B
Write
ΓcM:B
Γcprintm.M:B
Print
{ΓcMi:B}1in
Γcchoosei(Mi):B
Choose
{ΓcMs:B}sS
Γcreadtos(Ms):B
Read

Here sS ranges over a finite pointed state set, mM over a printing monoid, and eE over errors. The annotations on divergence, recursion, and error record the computation type that their premise does not otherwise determine. Recursion unfolding is included in judgmental equality: μzBMM[thunk(μzBM)/z]. The computation read of the opening is readtos(returns). Third, the sequencing rule itself changes, and that change is the subject of the chapter.

The judgments are ΓvV:A, ΓcM:B, and separate formation judgments for value and computation types. Contexts contain only value variables.

Definition 103.4 — The dCBPV rule delta

The adjunction formers have the rule card

ΓvV:A
ΓcreturnV:FA
Return
ΓcM:B
ΓvthunkM:UB
Thunk
ΓvV:UB
ΓcforceV:B
Force

Their computations are force(thunkM)0M,thunk(forceV)V. For a neutral value z:UB, forcez is a neutral computation and does not contract.

The minus calculus, dCBPV, sequences only into a type that does not depend on the returned value:

ΓcM:FAΓ,x:AcN:B
ΓcMtoxinN:B
Bind^-

where xFV(B).

The expression return3toxinreturn(replicatextrue) computes to a length-three vector. Rule Bind can type its term but cannot expose that length in the classifier of the whole computation. This is the concrete obstruction that forces the plus calculus.

Definition 103.5 — Dependent Kleisli extension

Let trx:=thunk(returnx). The plus calculus adds exactly

Γ,z:UFA,ΓB ctypeΓcM:FAΓ,x:A,Γ[trx/z]cN:B[trx/z]
Γ,Γ[thunkM/z]cMtoxinN:B[thunkM/z]
Bind^+

The classifier depends on a thunk z:UFA, not on an unperformed value. The beta equation is (returnV)toxinN0N[V/x].

For the vector computation, fix a value-type family ResultVec:UFNatU with defining equation ResultVec(trn)VecBn. Then form B(z):=F(ResultVec(z)). The family is a value-level classifier; it does not force z as a computation. The result type stores thunkM, so repeated type inspection does not rerun M.

Work one effectful program at that classifier. Let S={0,1,2} be the state set, let M:=readtos(returns):FNat read the cell, and let N:=return(replicatextrue). Rule Bind+ types MtoxinN at B[thunkM/z]=F(ResultVec(thunkM)). The postcondition genuinely depends on the returned value: in state s the program returns a vector of length s, and the classifier names the computation that produced it rather than any one of those lengths. Nothing here forces M: the classifier inspects thunkM, so type checking the program does not read the cell. What the classifier cannot do is reduce to VecB2 in state 2, because ResultVec computes only on trn.

Recovering it means running the machine, and that is where this program leaves the equality-preserving fragment. The transition from the state read M to its selected branch Ms is not a judgmental equality, so a classifier that distinguishes their thunks changes across the step. Rule Bind+ still types the source program. The base preservation claim cannot cover its read step; the extended claim below uses an explicit computation-type inclusion to retype the selected branch at the source read classifier. Replacing the cell by a fixed environment removes the problem and the effect together: with Me:=returne under a parameter e:E, the classifier may name e itself, no transition of the machine fires, and the first claim applies unchanged.

Definition 103.6 — Thunkability and linearity

Let Γ,z:UFAB(z) ctype. A computation M:FA is thunkable for B when every step MM satisfies B[thunkM/z]B[thunkM/z]. Let M:FA, N:FA, and let K(x,y) be a well-typed continuation with xFV(N) and yFV(M). The continuation is linear for M,N when the interchange equation Mtoxin(NtoyinK)Ntoyin(MtoxinK) holds at their common computation type. Both are properties of the printed family, computations, and continuation; neither is a typing judgment that every effect automatically satisfies, and neither is a notion of the selected source, which classifies effects by a different criterion recorded in lemma 103.9 below. They are used here only to sort effects, never as an unstated hypothesis of a theorem.

Sort the seven operations, and note at once that the sort does not follow intuition about which effects feel harmless. error is thunkable vacuously: it has no successor, so the quantification over steps is empty. Divergence is thunkable because its only successor is itself. Recursion is thunkable when its unfolding equation is included in judgmental equality. Each of write, print, choose, and readto fails thunkability for any family that distinguishes its source thunk from the thunk of the selected successor.

The last of those deserves its own sentence, because reading looks passive. It is not. readtos(Ms) does not compute a value from its own subterms; it selects the branch Ms indexed by the state the machine happens to be in, and that state is not part of the term. Two runs of the same closed computation therefore take different transitions, so no equation of the calculus can identify readtos(Ms) with the branch it selects. Reading is the read half of global state, and it is excluded from the first theorem below exactly as writing is.

A reader—a computation parameterized by an environment that is never written—is a different object, and it is not an effect of this calculus at all. Fix a value type E of environments and use a value parameter: Πe:EFA is an ordinary dependent computation type built from Bind and Bind+, its classifier may mention e directly rather than a thunk, and no transition of definition 103.8 fires for it. The distinction is exactly the one Bind+ was introduced to make: a value already produced may be named in a type, a computation that has not run may not.

Exercise 103.2

★★☆ Instantiate Bind+ with a closed M:FNat and the vector family above. Write the context and classifier before and after the beta step at M=return2. Explain why replacing thunkM by M is ill sorted.

Substitution and reduction

Lemma 103.7 — Value substitution

If ΓvV:A, then substitution of V for x:A preserves value-type formation, computation-type formation, value typing, and computation typing.

Proof of Lemma 103.7 — Value substitution

Proof. Prove the four claims simultaneously by induction on their derivations. A variable derivation for x becomes the given derivation of V; every other variable is unchanged. Under Σ, Π, and lambda binders, choose yFV(V)dom(Γ) and apply the induction hypothesis to the alpha-renamed body.

For Bind+, write its two bound names as x0, the value variable of tox0in, and z, the thunk variable of the classifier; alpha-rename both outside FV(V){x}. Substitute in the family formation premise, the computation premise, and the continuation premise. The two substitutions then commute, because no bound name of the rule occurs in V and x is neither x0 nor z. Thus B[thunkM/z][V/x]=B[V/x][thunk(M[V/x])/z], which is the conclusion classifier of the rebuilt rule. Return, thunk, force, ordinary bind, products, and their equations rebuild their induction hypotheses directly. Conversion substitutes into both sides of the equality derivation. These are all rule families of the displayed fragment. ◻

Subject reduction is stated for a machine rather than a term relation, because a dependent classifier is disturbed by what the surrounding stack does, not only by what the head computation does.

Definition 103.8 — Stacks and configurations

A stack Γ;BkK:C takes a computation of type B to one of type C. Stacks are generated by

Γ;Cknil:C
Nil
Γ,x:AcM:BΓ;BkK:C
Γ;FAk([]toxinM)::K:C
To
ΓvV:AΓ;B[V/x]kK:C
Γ;Πx:ABkV::K:C
Arg

A configuration is a pair M,K of a computation ΓcM:B and a stack Γ;BkK:C; its type is C. On the displayed term fragment, the pure transitions are (MV,K)  (M,V::K),(λx.M,V::K)  (M[V/x],K),(MtoxinN,K)  (M,([]toxinN)::K),(returnV,([]toxinN)::K)  (N[V/x],K),(force(thunkM),K)  (M,K),(divergeB,K)  (divergeB,K),(μzBM,K)  (M[thunk(μzBM)/z],K),(choosei(Mi),K)  (Mj,K)(1jn). Every (errorBe,K) is terminal. For printing and global state, a configuration carries two further components, an output m and a state s, and the transitions are (writes.M,K,m,s)  (M,K,m,s),(readtos(Ms),K,m,s)  (Ms,K,m,s),(printn.M,K,m,s)  (M,K,mn,s). All pure transitions preserve m and s. Initial configurations use the monoid unit and the pointed initial state; terminal configurations may carry any output and state.

Lemma 103.9 — Equality-preserved classifiers

Let Γ,z:UFAB ctype. If MM:FA, then B[thunkM/z]B[thunkM/z]. Call B sensitive to a transition (M,K)(M,K) when the displayed equality fails. Force/thunk beta, recursion unfolding, and the self-loop of divergence are insensitive for every well-formed B. The operational rules for writing, printing, choice, and reading provide no such judgmental equality; a family that distinguishes their two thunks is therefore sensitive to that step.

Proof of Lemma 103.9 — Equality-preserved classifiers

Proof. Congruence first gives thunkMthunkM. Substitution respects judgmental equality, which gives the displayed classifier equality. The force/thunk and recursion transitions are directed uses of their judgmental equations, and divergence has identical source and target, proving the three uniform claims.

No converse is asserted for an arbitrary family: a constant family cannot observe any transition. For a sensitive family, failure of the classifier equality is its defining property. A write or print step discards its outer operation, while a choice or read step selects one branch; none of these four root transitions is a judgmental equation of the selected signature. The inclusion rules in theorem 103.10 handle exactly these steps without pretending that all families distinguish them. ◻

Theorem 103.10 — Subject reduction

In absence of printing, global state, and erratic choice, every transition of the machine of definition 103.8 from a well-typed configuration results in a well-typed configuration of the same type. If the four computation-type inclusion rules

ΓcN:B[thunkM/z]
ΓcN:B[thunk(writes.M)/z]
Incl-Write
ΓcN:B[thunkMs/z]
ΓcN:B[thunk(readtos(Ms))/z]
Incl-Read
ΓcN:B[thunkM/z]
ΓcN:B[thunk(printm.M)/z]
Incl-Print
ΓcN:B[thunkMi/z]
ΓcN:B[thunk(choosei(Mi))/z]
Incl-Choose

are added, for every sS and 1in, then the same holds even with printing, global state, and erratic choice. Both claims are at the displayed value/computation syntax, the seven operation-typing rules, the recursion equation, and the transitions of definition 103.8; they assert neither normalization nor decidable conversion.

Proof of Theorem 103.10 — Subject reduction

Proof. Fix a transition from a configuration of result type C, and induct on the root transition. The induction invariant retains a middle computation type B: before and after the transition, the head has type B and the stack consumes B, up to the conversion rule in the first claim and up to one of the four displayed inclusions in the second claim.

For application push, inversion gives ΓcM:Πx:AB, ΓvV:A, and a stack consuming B[V/x]. Rule Arg therefore types V::K as a stack consuming Πx:AB. Lambda pop uses value substitution on the premise Γ,x:AcM:B, yielding ΓcM[V/x]:B[V/x]; the tail stack already consumes that type. Force/thunk contraction is typed by inversion of Force and Thunk. Divergence is a self-loop at its annotation, and recursion unfolding is typed by substituting thunk(μzBM):UB for z in the premise of Rec. An error configuration has no successor.

For sequencing push, inversion of Bind or Bind+ gives the head typing and the frame premise. Rule To rebuilds the stack in the minus case. In the plus case, retain the classifier family Γ,z:UFA,ΓB(z) ctype as part of the frame invariant. When the head reaches returnV, value substitution gives Γ,Γ[trV/z]cN[V/x]:B[trV/z]. Every intervening transition in the first claim is a directed judgmental equation: beta, force/thunk, recursion unfolding, or the divergence self-loop. Consequently MreturnV, and lemma 103.9 gives B[thunkM/z]B[trV/z]. Conversion therefore makes the residual head and tail stack meet at the original middle type. This proves the two sequencing roots, including the only case in which the middle type can mention the computation being run.

It remains to check the four observable roots admitted only by the second claim. Inverting Write and Print types the successor M at B[thunkM/z]; Incl-Write or Incl-Print converts that judgment to the classifier containing the discarded outer operation. Inverting Read or Choose types every branch, in particular the selected branch Ms or Mi, and Incl-Read or Incl-Choose converts the selected classifier to the source classifier. The output and state components do not occur in typing, so their update leaves the derivation unchanged. These four roots and the pure roots listed in definition 103.8 exhaust the machine. Hence the first claim holds without the four roots, and the second holds after adding their inclusions. ◻

The exclusions are substantive. Each of the four excluded operations replaces the term by a successor that judgmental equality need not identify with it. A family sensitive in the sense of lemma 103.9 therefore has different classifiers before and after the step. A write and a print discard an outer operation; a choice and a read select a branch on information the term does not contain. The inclusion rules of the second claim repair this by giving up uniqueness of typing rather than by proving the two types equal. Thunkability, in the sense of definition 103.6, is the corresponding property of one computation and one family; it is not a theorem that all state computations are harmless.

Translations and two bounded repairs

The call-by-value translation sends a source type A to a value type Av and a term to a computation Mv:FAv. A source variable remains a value, so substitution is restricted to values. The call-by-name translation sends a source type to a computation type and places assumptions under U; arbitrary substitution is available, while dependent elimination requires the storage/thunkability equation. Neither translation validates all three vertices of theorem 103.3.

Classical control produces another concrete failure, and it is worth printing because the repair is the same move as Bind+ made at a different level. Under the ordinary double-negation translation a computation of type A becomes (A+). The type of the second component of a dependent pair must then mention fste, a value that exists only inside a continuation and can never be returned, since the continuation’s answer type is .

A polymorphic-answer CPS translation changes the answer type from to a variable. Its computation translation is A÷:=Πα:.(A+α)α, so that the underlying value can be extracted by running the computation at the identity continuation: e÷A+id:A+. Two target rules make that extraction usable inside a type. The first records the value of a computation while checking a continuation,

Γe:Πα:.(Aα)αΓB:Γ,x=eAide:B
Γe@B(λx:A.e):B
T-Cont

and the second is the equation that justifies it, Γ(e1@B(λx:A.e2))(λx:A.e2)(e1Aid). Evaluation order is explicit in T-Cont: the continuation body e is checked under the hypothesis that x already equals the value that e returns, so the pair’s second component may mention it. Compare Bind+, which checks N under a classifier mentioning trx for the value x that M will return. Both rules buy dependency by naming the not-yet-produced value in the classifier; the source language differs, and so does what is proved. Its call-by-name and call-by-value translations preserve typing for the exact CoC-with-Π/Σ signatures of those translations. This fact does not prove that arbitrary control operators satisfy Bind+.

A fibred calculus repairs the same obstruction with a different rule. It adds a computational Sigma type: from Γ,x:AC it forms ΓΣx:AC, a computation type whose inhabitants package a value index with a computation in the corresponding fiber. Its introduction and elimination are V,M(x:A).C,Mtox:A,z:CinK, and the eliminator is typed in a third judgment Γz:CK:D of homomorphism terms, in which the computation variable z occurs linearly. That linearity is what fixes evaluation order: a computation bound to z always runs before the rest of K, so eliminating V,M runs M first.

The two repairs differ in where the not-yet-produced value is named. Bind+ keeps one judgment and moves the dependency into the classifier through a thunk; the fibred calculus adds a judgment and moves the dependency into a linear computation variable. On the vector example the package contains n:Nat and a computation returning VecBn, and no thunk appears. The rule delta has a different semantic model and donates no substitution or preservation theorem to definition 103.5.

Suggested first pass.

None of these problems is a prerequisite for a later chapter. Begin with exercise 103.3, then complete exercise 103.5.

Exercise 103.3

★★☆ Reconstruct the Fire Triangle proof using an identity type instead of Leibniz equality. State the eliminator used to distinguish true from false and identify the conversion that uses observability.

Exercise 103.4

★★★ Classify error, write, readto, choose, and a fixed-environment reader against thunkability. For each positive classification write the equation required by definition 103.6; for each negative one give a family B(z) and two states, or two branches, at which the two classifiers differ. Then say which of the five are admitted by the first claim of theorem 103.10 and which need an inclusion rule, and explain why the reader is on the admitted side for a reason that has nothing to do with thunkability.

Exercise 103.5

★★★ Practical project.dcbpv-dependent-sequencing-checker Implement in Kappa a finite checker and evaluator for values, computations, return, thunk, force, a state read, and dependent sequencing over natural-indexed vectors. Maintain the invariant that classifiers mention only value variables or thunks admitted by Bind+, so that sequencing a state read yields a classifier that names the thunk rather than any one state. The named acceptance cases are return-vector-2accepted: Vec 2,raw-effect-in-typerejected: computation in value position,force-neutralneutral computation,readto-classifieraccepted: thunked. A mutation that permits a raw computation in a classifier must fail the second oracle, and one that collapses a state read to a single state must fail the fourth. The checker illustrates subject reduction on a finite fragment; it does not prove the Fire Triangle or semantic soundness.

Sources. The no-go theorem and its CBV/CBN boundary are reconstructed from Pédrot and Tabareau, pp. 2–5 [PT20]. The dCBPV split, dependent Kleisli extension, translations, and effect-specific preservation boundary follow Vákár [Vá15]: the effect operations and their machine transitions are his Figures 8–10, the dependently typed stacks and configurations of definition 103.8 are his Figure 21, subject reduction is his Theorem 3.10, and the inclusion rules that recover it for printing, global state, and erratic choice are his Figure 24 and Theorem 3.11. Neither that paper nor his thesis uses the words thunkable and linear; definition 103.6 is chapter-local and is used only to sort effects. The CPS comparison is source-bounded to Bowman et al., whose answer-type-polymorphic computation translation and T-Cont rule are printed on their p. 22:8 [BCRA18]; the computational-Sigma comparison is source-bounded to Ahman, Ghani, and Plotkin, whose computation-term grammar and homomorphism judgment are their Figure 1 [AGP16].

Search the book

Type to search the local edition.