Lectures onType Theory
Chapter 23
Chapter 23Optional

Gradual Typing and the Dynamic Boundary

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

A dynamic helper may call a statically typed natural-number function with a Boolean. The unknown type is an annotation that postpones that comparison; elaboration turns it into a cast, an explicit labeled run-time check. Evaluation then reports the boundary owner instead of applying the function to the wrong value. Simply assigning the client value the universal static type Top from chapter 8 does not help: a value at Top cannot be applied. Erasing all types does not help either: the second execution still needs a run-time decision, but now the decision has no account of which boundary was violated. The unknown type must therefore carry two pieces of structure. It must permit a static program to postpone a comparison, and the postponed comparison must become an explicit run-time check with an owner. Two simpler alternatives fail. An unknown annotation that authorizes every operation without inserting a run-time check can erase the boundary entirely (as unchecked any can). An annotation erased before execution carries neither a check nor a label identifying the responsible boundary (as an ordinary Python type hint does). The unknown type here instead models the checked, blame-tracking alternative.

Consistency is not equality

The source language distinguishes a missing annotation from every ordinary type. We write ? for the missing information and reserve A,B,C for source and target types.

Definition 23.1 — The gradual source calculus

Let b range over true,false and let n range over natural-number literals. Every application carries a source position ; distinct applications have distinct positions. The gradual types are the source types generated by ordinary base and arrow types together with the unknown type ?: A,B::=2N?AB,e::=bnxλx:A.e(e1e2). Arrows associate to the right, so ABC abbreviates A(BC). The label is not inspected by source typing. Elaboration will derive from it two blame names, one for the function position and one for the argument position. Contexts are finite lists of declarations with distinct variables, so each variable has at most one declared type.

The consistency relation AcB is the least relation generated by

?cA
C-UnkL
Ac?
C-UnkR
2c2
C-Bool
NcN
C-Nat
A1cB1A2cB2
A1A2cB1B2
C-Arr

These rules are already closed under symmetry. Cast insertion is a function of the source position and endpoint types, so both derivations of ?c? insert the same cast.

Function matching is the partial operation fun(AB)=AB,fun(?)=??, and is undefined on 2 and N. Source typing is generated by the following rules.

Γb:2
G-Bool
Γn:N
G-Nat
x:AΓ
Γx:A
G-Var
Γ,x:Ae:B
Γλx:A.e:AB
G-Lam
Γe1:Cfun(C)=ABΓe2:DDcA
Γ(e1e2):B
G-App

There is no source subsumption rule and no rule that changes a derived type to ?. Unknown information enters only through written binder annotations and the matching operation in G-App.

Lemma 23.2 — Consistency reflexivity

For every type A, one has AcA.

Proof of Lemma 23.2 — Consistency reflexivity

Proof. Induct on A. The base cases are C-Bool and C-Nat; at ? use either unknown rule. If A=A1A2, apply the two induction hypotheses to C-Arr. ◻

Lemma 23.3 — Consistency symmetry

If AcB, then BcA.

Proof of Lemma 23.3 — Consistency symmetry

Proof. Induct on the displayed consistency derivation. The cases C-UnkL and C-UnkR exchange rules. The Boolean and natural cases reproduce the same rule. In C-Arr, apply the two induction hypotheses and rebuild C-Arr. These are all consistency rules. ◻

Consistency permits a local comparison without asserting equality. For example, 2c?and?cN,but2cN. Thus consistency is reflexive and symmetric but not transitive. Treating it as an equivalence relation would accept a direct Boolean–natural mismatch without recording the intervening unknown boundary.

Put apply:=λf:?.λx:?.(fx)0. Matching gives f the temporary shape ??, and the declared type of x satisfies ?c?, so f:?,x:?f:?fun(?)=??f:?,x:?x:??c?f:?,x:?(fx)0:?GAppf:?λx:?.(fx)0:??GLamapply:???GLam. The derivation authorizes a run-time check at 0; the target cast decides whether the actual argument tag matches the function’s domain.

Proposition 23.4 — Unique source types

If Γe:A and Γe:B, then A=B.

Proof of Proposition 23.4 — Unique source types

Proof. Induct on the syntax of e. Constants and variables have the unique types shown in their rules. A lambda’s domain is written in the term, and the induction hypothesis fixes its codomain. For an application, the induction hypothesis fixes the type C of the function position. The partial function fun has at most one result, so its codomain B is fixed. The consistency premise checks the argument but does not choose the result. ◻

Exercise 23.1

★★☆ Give derivations of 2c? and ?cN. Prove by inversion that 2cN. Then attempt to prove transitivity of c by induction on its first derivation and identify the case in which the required second derivation has no invertible outer constructor.

Exercise 23.2

★☆☆ Derive the type of ((λg:?.(g0)1)(λn:N.n))2. Then replace the last binder annotation by 2. Does source typing reject the program, or does it postpone the mismatch? Name the premise responsible.

The ground-cast target

The cast calculus executes labeled boundaries. A blame name has two faces, p and p¯, with p¯=p. In a boundary cast, p names the provider of the value and p¯ names its context.

Definition 23.5 — Ground types, casts, and target typing

Ground types are the run-time tags: G::=2N??. For A?, define its ground shape by gnd(2)=2,gnd(N)=N,gnd(AB)=??. Target terms are a::=bnxλx:A.aa1a2BApablamep. BApa checks a value already typed at A and returns it at B, or produces blame p. We write ΓCa:A for typing in this cast calculus, to distinguish it from source typing Γe:A.

ΓCb:2
T-Bool
ΓCn:N
T-Nat
x:AΓ
ΓCx:A
T-Var
Γ,x:ACa:B
ΓCλx:A.a:AB
T-Lam
ΓCa1:ABΓCa2:A
ΓCa1a2:B
T-App
ΓCa:AAcB
ΓCBApa:B
T-Cast
ΓCblamep:B
T-Blame

Here B may be any type in the type grammar. Consequently target typing is not unique for blame: the same blamep has every well-formed result type. The unique-type property proposition 23.4 applies to source typing. Target application therefore requires an actual arrow type and exact agreement at its argument; consistency is confined to T-Cast.

The last two value constructors represent a delayed function check and a ground-tag injection; both v and w range over values: v::=bnλx:A.aA2B2A1B1pv?Gpv. The second line is a function wrapper. The third is a tagged injection into the unknown type. A cast is a value only in these two forms.

A wrapper stores (A1B1,A2B2,p,v). On application it constructs the domain cast at p¯ and result cast at p without consulting source syntax.

Definition 23.6 — Call-by-value reduction

Evaluation frames and contexts are F::=[]av[]BAq[],E::=[]F[E]. Reduction is compatible with these contexts and is generated by the following contractions. In the two decomposition rules, A is neither ? nor ground.

(λx:A.a)va[v/x]
E-Beta
B{2,N}
BBpvv
E-IdBase
??pvv
E-IdUnk
G?p(?Gqv)v
E-Project
G1G2
G2?p(?G1qv)blamep
E-Mismatch
A?Agnd(A)
?Apv?gnd(A)p(gnd(A)Apv)
E-Ground
A?Agnd(A)
A?pvAgnd(A)p(gnd(A)?pv)
E-Expand

The function rule is the only contraction that changes polarity.

u=A2B2A1B1pvw value
uwB2B1p(v(A1A2p¯w))
E-WrapApp
F[blamep]blamep
E-Blame

In this rule, Ai is the domain and Bi the codomain on side i. The value premise is load bearing: a nonvalue argument must first step in the frame u[], which preserves determinism. The compatible closure places one contraction in an evaluation context. In particular, E-Blame crosses one frame at a time; this avoids the multiple successors that would arise if an arbitrary multi-frame context could discard itself in one step.

The ground restriction is not cosmetic. An injection stores one of three tags, not an arbitrary syntax tree of types. A function of type NN is first wrapped at ?? and only then injected. Projection reverses those two steps. This is what lets a dynamic function cross a boundary without pretending that its latent argument and result checks have already happened. If arbitrary types were admitted as dynamic tags, the existing roots would not be exhaustive. For example, compare a requested NN with a stored tag ?N: NN?q(??Npv). The tags are neither equal, so E-Project cannot apply, nor distinct ground tags, so E-Mismatch has no premise. Factoring both arrows through the single tag ?? restores an exhaustive equality test and leaves domain and codomain checks to E-WrapApp.

Example 23.7 — A successful tag round trip

Let d=?Np0. Then N?qd0 by E-Project. In contrast, 2?qdblameq by E-Mismatch. The injection’s label p is not blamed: the projection at q made the false promise that a value tagged N was a Boolean.

Elaboration exposes the boundary

Define the two roots deterministically by f:=(,fun) and a:=(,arg), and use their positive faces as the provider labels. All such roots are distinct because source positions are distinct, so elaboration needs no mutable fresh-name supply. The subscripts in i,f and o,a abbreviate (i)f and (o)a. The elaboration judgment Γea:A has the same constant, variable, and lambda structure as source typing. Its application rule inserts exactly the checks justified by matching and consistency.

Definition 23.8 — Cast insertion

The rules are

Γbb:2
I-Bool
Γnn:N
I-Nat
x:AΓ
Γxx:A
I-Var
Γ,x:Aea:B
Γλx:A.eλx:A.a:AB
I-Lam
Γe1a1:Cfun(C)=ABΓe2a2:DDcA
Γ(e1e2)(ABCfa1)(ADaa2):B
I-App

The two derived labels are globally distinct. Cast insertion retains identity casts, whose reductions are defined by the target semantics.

Proposition 23.9 — Insertion is total, unique, and typed

If Γe:B, there is a unique target a, up to the fixed fresh label convention, such that Γea:B. Moreover ΓCa:B.

Proof of Proposition 23.9 — Insertion is total, unique, and typed

Proof. Use proposition 23.4 and induct on that source typing derivation. The first four cases are homomorphic. The application premises include fun(C)=A0BandDcA0. Lemma 23.2 gives CcA0B: it is equality when C is an arrow, and follows from C-UnkL when C=?. Hence T-Cast types the function cast at A0B and the argument cast at A0; target application yields B. All choices were fixed by the source derivation and the fresh-label convention. ◻

For a closed source term, write er when there are a,A such that ea:A,ar,r is a value or labeled blame. Thus packages the separate elaboration and target-evaluation judgments; it is not another target reduction relation.

Example 23.10 — The elaborated mixed application

Let i=λn:N.n, and put e0=((λf:?.(f0)i)i)o,eb=((λf:?.(ftrue)i)i)o. Both source terms have type ?. To keep the target calculation readable, define z0=?Ni,a0,zb=?2i,atrue,hz(f)=(???i,ff)z,gz=λf:?.hz(f),uz=????o,fgz,d=?NNo,ai. Here hz(a) is meta-level substitution of a for the written occurrence f. The inner application inserts the function projection and dynamic argument injection; the outer application injects the typed identity before passing it to dynamic code. Rule I-App, used first at i and then at o, gives e0uz0d:?,ebuzbd:?. First, in either program, E-Ground changes d to the value d:=???o,a(??NNo,ai). For z{z0,zb}, the common prefix is uzdEWrapApp??o,f(gz(??¯o,fd))EIdUnk??o,f(gzd)EBeta??o,fhz(d)EProject??o,f(wz), Its ground tag already is ??, so E-Expand does not apply. The projected payload itself is the wrapper value w:=??NNo,ai. Applying this wrapper gives wzEWrapApp?No,a(i(N?¯o,az)). E-WrapApp casts the argument from ? to N at ¯o,a before applying i. A natural tag projects to 0; a Boolean tag produces blame¯o,a. For z=z0, the remaining roots yield uz0dEProject,EBeta,EIdUnk?No,a0. For z=zb, mismatch and blame propagation instead give uzbdEMismatch,thenEBlameblame¯o,a. The two source-to-result outcomes are e0?No,a0,ebblame¯o,a. The failing face is the complement of the label on the typed function’s passage into the dynamic argument position. The target therefore records both the failed check and its owner.

Exercise 23.3

★★☆ Write the complete target elaborations of ((λx:?.x)0)1and((λx:2.x)((λy:?.y)0)2)3. Keep every identity cast and give every generated cast its indicated fresh function or argument label.

Safety permits blame, not stuck casts

Blame is a permitted outcome, not a failure of the theorem. It is not a value, and safety does not promise to exclude it. Safety promises only that a closed typed target always has a next step until it reaches a value or a labeled boundary failure.

Lemma 23.11 — Target substitution

If Γ,x:ACa:B and ΓCv:A, then ΓCa[v/x]:B.

Proof of Lemma 23.11 — Target substitution

Proof. Induct on the typing derivation of a. Variable and binder cases use the usual capture-avoiding renaming. In the cast case, apply the induction hypothesis to the operand and retain the same consistency premise and label. Blame contains no variables. The application case uses the two induction hypotheses and reconstructs the exact target application rule. ◻

Lemma 23.12 — Canonical forms

For a closed target value v:

  1. if Cv:2, then v is true or false;

  2. if Cv:N, then v is a numeral;

  3. if Cv:AB, then v is a lambda or a function wrapper;

  4. if Cv:?, then v=?Gpw, whose outer tag G is uniquely determined by the value syntax.

Proof of Lemma 23.12 — Canonical forms

Proof. Inspect the value grammar and invert its typing. A Boolean or numeral has its declared base type. A lambda has an arrow type. A wrapper is typed at its target arrow, and an injection is typed at ?. No other value form exists. ◻

Lemma 23.13 — A cast on a value progresses

Suppose Cv:A, AcB, and c=BApv. Then c is a value or reduces in one uniquely determined step.

Proof of Lemma 23.13 — A cast on a value progresses

Proof. Analyze A and B. If both are the same base type, use E-IdBase; if both are ?, use E-IdUnk. If A and B are arrows, the term is a wrapper value. If B=? and A is ground, it is an injection value; if A is a non-ground arrow, use E-Ground. It remains that A=? and B?. If B is non-ground, use E-Expand. If B=G2 is ground, canonical forms writes v=?G1qw. Exactly one of G1=G2 and G1G2 holds, so exactly one of E-Project and E-Mismatch applies. Consistency excludes all remaining base–base and base–arrow pairs. ◻

Exercise 23.4

★☆☆ Starting with a closed v:NN, reduce ?NNpv until it is a value. List its outer tag and its inner wrapper.

Exercise 23.5

★★☆ Suppose E-Ground injected an arbitrary non-ground type directly, so that every A were allowed as a run-time tag. Using the displayed value grammar and lemma 23.12, identify which injection-value clause, projection clause, and canonical-form case would have to change. For each of the three, write one replacement clause or rule and one sentence explaining why the old form no longer covers the new tag. This is a different cast semantics, not a harmless implementation shortcut.

Theorem 23.14 — Preservation

If ΓCa:A and aa, then ΓCa:A.

Proof of Theorem 23.14 — Preservation

Proof. Induct through the evaluation context and then inspect the contracted redex. Rule E-Beta is lemma 23.11. Identity and matching projection remove casts whose operand already has the target type. For E-Mismatch, rule T-Blame assigns the surrounding result type.

For E-Ground, consistency gives Acgnd(A) and gnd(A)c?. Two applications of T-Cast therefore yield the target type ?. The dual argument handles E-Expand.

For the load-bearing wrapper case, inversion gives v:A1B1,w:A2,A1cA2,B1cB2. The domain cast labeled p¯ has type A1, so v(A1A2p¯w):B1. The cast is well formed because lemma 23.3 changes A1cA2 into A2cA1. The result cast labeled p then has type B2, the type of the original application. Context closure replaces a subterm by another of the same type. Blame propagation is typed by T-Blame at the context’s result type. ◻

Theorem 23.15 — Progress and determinism

If Ca:A, exactly one of the following holds: a is a value, a=blamep for some p, or there is a unique a with aa.

Proof of Theorem 23.15 — Progress and determinism

Proof. Induct on the target typing derivation. Constants and lambdas are values; blame is the second alternative. In an application, first use the induction hypotheses in left-to-right order. If either subterm is blame, E-Blame applies. When both are values, lemma 23.12(3) writes the function as a lambda or wrapper, so E-Beta or E-WrapApp applies. In a cast, first progress its operand; when the operand is a value, apply lemma 23.13. For uniqueness, an application with a nonvalue operator has only the []a frame; once the operator is a value, a nonvalue argument has only the v[] frame; once both are values, canonical forms selects exactly one of E-Beta and E-WrapApp. A cast with a nonvalue operand has only its cast frame; at a value operand, lemma 23.13 selects one root, using the decidable equality test for two ground tags. Finally E-Blame crosses the unique surrounding frame. Thus no term has two successors. ◻

Corollary 23.16 — Safety of elaborated programs

If ea:A, then every finite reduction of a ends at a value, a labeled blame term, or a term with a unique next step. It never ends at an unclassified stuck term.

Proof of Corollary 23.16 — Safety of elaborated programs

Proof. By proposition 23.9, the elaborated target has type A. Apply preservation along the finite reduction and then theorem 23.15 to its endpoint. The three alternatives there are exactly a value, labeled blame, or a unique next step, so no fourth stuck form is possible. ◻

Who can be blamed?

Safety alone permits every typed program to fail. Blame safety says which face of a boundary cannot be the failure. It uses two mutually defined orders because a function wrapper changes the direction at its domain.

Definition 23.17 — Positive and negative safety

The judgments A+B and AB are the least relations generated by the following rules.

2+2
P-Bool
N+N
P-Nat
A+?
P-Unk
B1A1A2+B2
A1A2+B1B2
P-Arr
22
N-Bool
NN
N-Nat
?A
N-Unk
G is groundAG
A?
N-GroundUnk
B1+A1A2B2
A1A2B1B2
N-Arr

Positive safety means that a cast from the left type to the right type cannot blame its provider face. Negative safety means that it cannot, after any number of wrapper applications, blame its context face. The asymmetric ground rule is necessary: casting a base value to ? cannot later inspect an argument, while casting a function to ? installs a wrapper whose domain can blame the dynamic context.

Proof of Proposition 23.18 — Decidability of polar safety

Proof. Use a structural checker for every rule except N-GroundUnk. For a negative goal ending in ?, try the finite set G{2,N,??} and then call the structural checker with N-GroundUnk disabled. Every auxiliary call recurses on a proper type component. Termination is lexicographic: the exceptional call changes the phase from ground selection to structural checking, and every call within the structural phase decreases total type size. The rule list is syntax directed in that phase, while the finite ground trial enumerates every possible last use of N-GroundUnk; hence the checker is sound and complete. ◻

The asymmetry is visible in the smallest arrow example: NN+?by PUnk,NN⪯̸?. For the missing negative derivation, N-GroundUnk would require a ground G with NNG. The only possible arrow-shaped ground tag is G=??, but N-Arr would then require ?+N, for which there is no rule.

Lemma 23.19 — Polar reflexivity

For every type A, both A+A and AA.

Proof of Lemma 23.19 — Polar reflexivity

Proof. Proceed simultaneously by induction on A. The base cases are P-Bool, N-Bool, P-Nat, and N-Nat. At ? use P-Unk and N-Unk. For A=A1A2, apply P-Arr to the negative induction hypothesis on A1 and the positive hypothesis on A2; apply N-Arr to the positive hypothesis on A1 and the negative hypothesis on A2. ◻

Lemma 23.20 — Grounding preserves polar safety

For A?:

  1. if A+?, then A+gnd(A) and gnd(A)+?;

  2. if A? and A is not ground, then Agnd(A) and gnd(A)?;

  3. if ?A, then ?gnd(A) and gnd(A)A.

Proof of Lemma 23.20 — Grounding preserves polar safety

Proof. If A is a base type, then gnd(A)=A. Clauses 1 and 3 are therefore polar reflexivity followed or preceded by P-Unk or N-Unk. Clause 2 assumes that A is not ground, so it has no base case.

Let A=A1A2. Then gnd(A)=??. For clause 1, P-Arr uses ?A1,A2+?, from N-Unk and P-Unk; another P-Unk relates the ground arrow to ?. For clause 2, inversion of N-GroundUnk gives AG. Arrow inversion and groundness force G=??=gnd(A); polar reflexivity and N-GroundUnk give the second judgment. For clause 3, N-Unk gives ?gnd(A), and N-Arr uses A1+? and ?A2 for the remaining judgment. ◻

Definition 23.21 — Label safety

Fix one face q. A target term is q-safe when it contains no occurrence of blameq, every cast carrying q has source S and target T with S+T, and every cast carrying q¯ has ST. Casts with unrelated labels are unrestricted.

Lemma 23.22 — One-step preservation of label safety

If a is well typed and q-safe and aa, then a is q-safe. No q-safe redex contracts directly to blameq.

Proof of Lemma 23.22 — One-step preservation of label safety

Proof. Beta reduction may duplicate existing casts, but it does not change their endpoints or labels. The other noncast reductions retain or discard existing labels. The ground and expansion cases use the applicable clause of lemma 23.20 to justify the two new casts with the old label. The remaining expansion case is vacuous: it would require ?+A for non-ground A, and no rule derives that judgment.

For E-WrapApp, first suppose its label is q. Positive safety of A1B1+A2B2 inverts to A2A1 and B1+B2. These are exactly the safety obligations for the new domain cast labeled q¯ and result cast labeled q. If the wrapper label is q¯, negative safety inverts to A2+A1 and B1B2, again exactly matching the swapped labels.

Only E-Mismatch creates blame directly. A mismatch that produced blameq would have outer cast G?q. But ?+G has no derivation for ground G; this contradicts q-safety. A mismatch labeled q¯ may instead create blameq¯, which is not excluded. A q-safe term contains no blameq, so E-Blame can propagate only an unrelated or complementary blame. Context closure preserves these observations. ◻

Theorem 23.23 — Positive and negative blame

If a well-typed term a is q-safe, then ablameq. Consequently, if a contains one boundary cast TSpv and no other occurrence of p or p¯, then

  1. S+T implies ablamep;

  2. ST implies ablamep¯.

Proof of Theorem 23.23 — Positive and negative blame

Proof. For the first sentence, induct on the length of a purported reduction to blameq. Length zero is excluded by the definition of q-safety. At positive length, repeated use of lemma 23.22 says every predecessor is q-safe, but the lemma excludes the final contraction that first creates blameq. Blame propagation cannot be first: its premise already contains that blame.

For clause 1, the only p-labeled cast has positive-safe endpoints and there is no p¯-labeled cast initially, so a is p-safe. For clause 2, instantiate the first sentence with q:=p¯. Since p¯=p, the original p-labeled cast is exactly a cast carrying q¯; its negative-safe endpoints satisfy the definition of p¯-safety for a. Apply the first sentence. ◻

Corollary 23.24 — Ownership at a typed–dynamic boundary

Suppose a well-typed closed term contains no occurrence of p or p¯ except the boundary cast named in the applicable clause.

  1. In ?Apv, the statically typed provider owns p and cannot be blamed at p.

  2. In A?pv, the statically typed context owns p¯ and cannot be blamed at p¯.

Thus a failure at either named boundary cannot be assigned to its more-informative side. Other, unrelated labels may still be blamed.

Proof of Corollary 23.24 — Ownership at a typed–dynamic boundary

Proof. Rule P-Unk gives A+? in the first clause, so theorem 23.23 excludes blamep. Rule N-Unk gives ?A in the second clause, so the same theorem excludes blamep¯. The freshness hypothesis ensures the corresponding label-safety premise in each case. ◻

In example 23.10, the typed identity function crosses into the dynamic argument position at o,a. The successful call returns a tagged natural. The Boolean call fails at ¯o,a, exactly the context face permitted by the first clause. The reported label names the boundary at which the typed function crossed into dynamic code, not the inner call that later supplied the offending Boolean. Those source locations coincide only for first-order casts.

Example 23.25 — The domain reverses responsibility

Let i=λn:N.n and let d=?2rtrue. The typed function is exposed at the canonical dynamic function type and then called by a dynamic context: (??NNpi)d. Rule E-WrapApp gives ?Np(i(N?p¯d)). The inner projection sees a 2 tag and reduces to blamep¯; propagation returns that blame. The cast’s codomain kept p, but its domain used p¯. Since NN+??, the provider p could not be blamed. The dynamic context supplied the bad argument and receives the complement.

In the other direction let k=λx:?.?2rtrue, a target value of type ??, and cast it to NN. Application to 0 checks the argument successfully, but the result is tagged 2 and its projection to N produces blameq. Because ??NN, the typed context face q¯ is protected; the dynamic provider face q is not.

Exercise 23.6

★☆☆ Derive NN+?? and ??NN. Every arrow-domain premise must name the opposite polarity.

Removing information

Blame compares the two faces of one boundary. Precision compares two programs by how much static type information they retain; our orientation is fixed throughout: AtyBmeans that A is more precise than B. It is not the domain-approximation order D used for recursive types; no precision fact crosses between the two.

Definition 23.26 — Type, context, and source-term precision

Type precision is generated by

Aty?
Pr-Unk
2ty2
Pr-Bool
NtyN
Pr-Nat
A1tyB1A2tyB2
A1A2tyB1B2
Pr-Arr

Contexts are related when they have the same variables in the same order and corresponding declared types are related:

ctx
PrCtx-Empty
ΓctxΓAtyA
Γ,x:ActxΓ,x:A
PrCtx-Extend

Source-term precision is the least compatible relation generated by

bsrcb
PrTm-Bool
nsrcn
PrTm-Nat
xsrcx
PrTm-Var
AtyAesrce
λx:A.esrcλx:A.e
PrTm-Lam
e1srce1e2srce2
(e1e2)src(e1e2)
PrTm-App

Programs being compared have the same unannotated syntax and source positions. Precision changes binder annotations only; it does not rewrite constants or rearrange applications.

Proposition 23.27 — Precision factors through polar safety

For all types A and B, AtyBA+B  and  BA.

Proof of Proposition 23.27 — Precision factors through polar safety

Proof. For the forward implication, induct on AtyB. At B=? the two judgments are P-Unk and N-Unk. The two base cases use the corresponding reflexive polar rules. At arrows, apply the two component induction hypotheses: P-Arr uses the negative domain judgment and positive codomain judgment, while N-Arr uses the other two.

Conversely, induct on B. If B=?, conclude by Pr-Unk. If B is a base type, inversion of A+B forces the same base type, so use Pr-Bool or Pr-Nat. If B=B1B2, inversion of the positive judgment forces A=A1A2. Inverting both polar judgments gives A1+B1,B1A1,A2+B2,B2A2. The induction hypotheses and Pr-Arr finish the proof. ◻

Lemma 23.28 — Matching and consistency lose information monotonically

Suppose AtyA, fun(A)=BC, DtyD, and DcB. Then fun(A)=BC for some B,C such that BtyB,CtyC,DcB.

Proof of Lemma 23.28 — Matching and consistency lose information monotonically

Proof. First invert precision. If A is an arrow, its two component premises give the result. If A=?, then fun(A)=??, and both components of the precise arrow are more precise than ?. No other A is possible because a matchable precise type cannot become a different base type.

For the consistency claim, use the auxiliary statement by induction on the derivation of DcB: if DtyD and BtyB, then DcB. For C-UnkL, precision from ? forces D=?, so C-UnkL applies; C-UnkR is symmetric. For C-Bool and C-Nat, each less-precise endpoint is either the same base or ?, and the corresponding base or unknown rule applies. For C-Arr, either successor is ?, closing by an unknown rule, or both successors are arrows; invert the two precision derivations, apply the induction hypotheses to domain and codomain, and use C-Arr. Since matching has established fun(A)=BC, applying the auxiliary statement to DcB, DtyD, and BtyB derives DcB. ◻

Theorem 23.29 — Static gradual guarantee

If ΓctxΓ, esrce, and Γe:A, then there is an A such that Γe:AandAtyA. Thus replacing annotations by ? preserves typability.

Proof of Theorem 23.29 — Static gradual guarantee

Proof. Induct on the derivation of esrce while inverting the typing of e. Constants are unchanged. The variable case uses context precision. For lambdas, the written domain precision and the induction hypothesis for the body give G-Lam and Pr-Arr.

For applications, write the precise premises as Γe1:C,fun(C)=BA,Γe2:D,DcB. The two induction hypotheses give CtyC and DtyD together with typings of the less precise subterms. Apply lemma 23.28 with its variables (A,A,B,C,D,D) instantiated as (C,C,B,A,D,D). The lemma gives fun(C)=BA with BtyB and AtyA, as well as DcB. Rule G-App therefore types the less precise application at A. ◻

The converse is false, and should be false. Starting with an unannotated program, inserting an incompatible precise annotation may produce a static error. The theorem runs from a checked precise program toward less information, not from arbitrary dynamic syntax toward arbitrary annotations. For example, let r=((λx:N.x)true),r=((λx:?.x)true). Then rsrcr and r:?, but r has no type: its application premise would require 2cN. Thus a reverse static guarantee cannot start merely from the typing of the less precise program.

Exercise 23.7

★★☆ Repeat the application case of theorem 23.29 for the special case in which the precise function has an arrow type and the less precise function has type ?. Display the new matching result and consistency derivation.

Exercise 23.8

★★☆ Write the complete typing derivation for r and invert a hypothetical typing derivation for r until it requires 2cN. Which direction of theorem 23.29 remains valid?

The dynamic gradual guarantee

Source terms execute only after elaboration, so precision must cross cast insertion. Extra casts appear on either side: replacing an annotation by ? can remove one check and create another at a later use. A precision relation that required identical target syntax would therefore be too weak.

Definition 23.30 — Typed target precision

Target precision relates elaborations with the same untyped source shape even when one side has already decomposed a cast. A precision context has the form Δ=(x1:A1tyA1,,xn:AntyAn). Its left and right projections are ΔL=(x1:A1,,xn:An) and ΔR=(x1:A1,,xn:An). For an ordinary context Γ=(x1:A1,,xn:An), write ΔΓ:=(x1:A1tyA1,,xn:AntyAn) for its diagonal precision context. The judgment Δa:ACa:A is the least relation generated by the following rules.

Δb:2Cb:2
CPr-Bool
Δn:NCn:N
CPr-Nat
x:AtyAΔ
Δx:ACx:A
CPr-Var
AtyAΔ,x:AtyAa:BCa:B
Δλx:A.a:ABCλx:A.a:AB
CPr-Lam
Δa1:ABCa1:ABΔa2:ACa2:A
Δa1a2:BCa1a2:B
CPr-App

Aligned casts retain the same boundary name:

Δa:SCa:SScTScTTtyT
ΔTSpa:TCTSpa:T
CPr-Cast

Reduction can decompose a cast on only one side:

Δa:SCa:UScTTtyU
ΔTSpa:TCa:U
CPr-CastL
Δa:SCa:UUcVStyV
Δa:SCVUpa:V
CPr-CastR

Rule CPr-CastL retains the left cast, whereas CPr-CastR retains the right cast. Each premise names the common type that relates the unaligned endpoints. A more precise program may fail where its less precise mate continues:

ΔRCa:AAtyA
Δblamep:ACa:A
CPr-Blame

There is no rule relating an arbitrary left term to right-hand blame. The two unaligned cast rules use opposite common bounds. In CPr-CastL, StyU comes from the term premise and TtyU is explicit, so U is a common less-precise upper bound of the left cast’s endpoints. In CPr-CastR, the term premise gives StyU and the last premise gives StyV, so S is a common more-precise lower bound of the right cast’s endpoints.

Proposition 23.31 — Regularity of target precision

Every derivation of Δa:ACa:A contains derivations of ΔLCa:A,ΔRCa:A,AtyA.

Proof of Proposition 23.31 — Regularity of target precision

Proof. Induct on target precision. The constant, variable, lambda, and application cases give the corresponding target typing rules. Rule CPr-Cast uses its two consistency premises. Rule CPr-CastL types the left cast and has TtyU; rule CPr-CastR types the right cast and has StyV. Rule CPr-Blame uses T-Blame on the left and its right typing premise. ◻

Lemma 23.32 — Target precision reflexivity

If ΓCa:A, then under the diagonal paired context, ΔΓa:ACa:A.

Proof of Lemma 23.32 — Target precision reflexivity

Proof. Induct on target typing. Constants, variables, lambdas, and applications use the corresponding precision rules and type-precision reflexivity. A cast uses CPr-Cast, its typing consistency premise twice, and the induction hypothesis. Blame uses CPr-Blame with its own target typing derivation. ◻

Lemma 23.33 — Insertion preserves precision

Suppose ΓctxΓ, esrce, Γea:A,Γea:A. Let ΔΓ,Γ contain x:BtyB exactly when the corresponding declarations x:B and x:B occur in Γ and Γ. Then ΔΓ,Γa:ACa:A.

Proof of Lemma 23.33 — Insertion preserves precision

Proof. Induct on source-term precision, using the static guarantee to obtain the less precise typing. Constants, variables, and lambdas use compatible target precision. In an application, the induction hypotheses relate the two elaborated subterms, while lemma 23.28 relates both matching results. Compare the two inserted function casts and the two inserted argument casts. Both applications insert corresponding casts, so CPr-Cast applies to each pair: matching gives precision of both function-cast targets and result types, and the application consistency premise gives precision of the argument-cast targets. Rule CPr-App then relates the target applications. No unaligned cast rule is needed at insertion time; CPr-CastL and CPr-CastR become necessary only after reduction decomposes one cast earlier than its mate. ◻

The operational proof begins with open substitution; beta reduction cannot be proved from a relation restricted to closed terms.

Definition 23.36 — Related evaluation frames

Write ΔF:STFF:ST for the judgment generated by the following rules. It assigns input types S,S and output types T,T and includes StyS and TtyT. The plugging property is proved afterward in lemma 23.37. The aligned frame rules are

Δa:ACa:ABtyB
Δ[]a:(AB)BF[]a:(AB)B
FPr-AppL
Δv:ABCv:AB
Δv[]:ABFv[]:AB
FPr-AppR
StySScTScTTtyT
ΔTSp[]:STFTSp[]:ST
FPr-Cast

Lift frame precision to evaluation contexts with the following two composition rules:

StyS
Δ[]:SSF[]:SS
FPr-Hole
ΔE:STFE:STΔF:TUFF:TU
ΔF[E]:SUFF[E]:SU
FPr-Cons

An unmatched cast is kept at the term-relation root by CPr-CastL or CPr-CastR; it is not silently treated as an aligned frame.

Lemma 23.37 — Frame plugging

If ΔE:STFE:ST and Δa:SCa:S, then ΔE[a]:TCE[a]:T. If the next left reduction lies in the hole of two related frames, a simulation of the hole reduction lifts through those frames.

Proof of Lemma 23.37 — Frame plugging

Proof. Induct on the construction of the related contexts. The empty contexts use the term premise. Rules FPr-AppL and FPr-AppR give CPr-App; FPr-Cast gives CPr-Cast. Replacing the hole term by its simulated residual preserves the same frame premises. ◻

Lemma 23.38 — Common precision fixes a cast's shape

If RtyS and RtyT, then:

  1. if S and T are ground, they are the same ground type;

  2. if one of S,T is a base type, the other is that base type or ?;

  3. if S=S1S2 and T=T1T2, then R=R1R2 with RitySi and RityTi for i=1,2.

Proof of Lemma 23.38 — Common precision fixes a cast's shape

Proof. For clause 1, if S is a ground base, inversion of RtyS gives R=S; inversion of RtyT then gives T=S because T is ground. If S=??, inversion gives R=R1R2; the second derivation can end at a ground type only at the same ground arrow. For clause 2, inversion at a base endpoint gives either the same base reflexivity rule or the rule whose target is ?. For clause 3, both derivations ending at arrows must use Pr-Arr; their premises give the four stated component relations. These exhaust the type grammar. ◻

Lemma 23.40 — Value catch-up

If v is a closed value and v:ACa:A, then av for a closed value v such that v:ACv:A.

Proof of Lemma 23.40 — Value catch-up

Proof. Induct on the precision derivation. Constants and lambdas already have a value on the right. Application cannot conclude a left value, and blame is not a value.

For CPr-Cast, a left cast value is an arrow wrapper or a ground injection. Apply the induction hypothesis to the related right operand. Now inspect the right cast endpoints. Equal bases and two unknowns reduce by identity. An arrow-to-arrow cast is already a wrapper. A ground-to-unknown cast is already an injection. A non-ground-arrow-to-unknown cast takes one E-Ground step and becomes an injection containing a wrapper. An unknown-to-arrow cast first takes E-Expand. Its inner ground projection then sees an injection. Clause 2 of lemma 23.39 gives either the same stored tag on the left or a common precise type below the stored and requested ground tags; lemma 23.38(1) equates those tags. Hence E-Project exposes a value and the outer arrow cast is a wrapper. If the target is a ground base, canonical forms likewise makes the operand an injection and tag equality selects E-Project, not E-Mismatch. These cases exhaust consistent endpoints. After operand catch-up they use at most two root contractions, so no termination argument is hidden in this step.

For CPr-CastL, remove the unmatched left wrapper or injection, catch up the right operand, and reapply CPr-CastL. For CPr-CastR, first catch up its operand. Equal endpoints reduce by identity; a ground injection is a value; a non-ground injection grounds; a matching projection projects; and an arrow cast is a wrapper. The same one-sided inversion in lemma 23.39(2), followed by lemma 23.38(1), shows that projection cannot take E-Mismatch. The aligned or one-sided cast rule then relates the two resulting values. ◻

Definition 23.41 — Stutter measure

Grounding and expansion replace one cast descriptor by two. For a cast descriptor put χ(T,S):={3T=? and S is neitherunknown nor ground,3S=? and T is neitherunknown nor ground,1otherwise. Thus E-Ground and E-Expand change 3 to 1+1. Every other cast contraction removes at least one unit; projection removes two descriptors, and mismatch also discards its payload.

Define the total cast weight cw(a) structurally: constants, variables, and blame contribute zero; an abstraction contributes the weight of its body; application adds the weights of its two subterms; and cw(TSpa)=χ(T,S)+cw(a). Let sz(a) be ordinary syntax-tree size, counting every constructor once, and put st(a):=(cw(a),sz(a))N×N, ordered lexicographically. This total measure counts casts in a waiting argument and underneath values, so moving the active position from a completed function to its argument does not increase it. It is used only when the right program takes zero steps.

The measure counts descriptors that no evaluation frame has reached yet. For example, let z=?Nq0,m=2?pz,a=?2rm. Two uses of CPr-CastL derive aCz. The three descriptors have total cast weight three. The inner mismatch produces ?2rblamep; the outer pending cast is the only remaining descriptor, so the first component drops to one. Its propagation to blamep lowers that component to zero. Hence the lexicographic measure strictly descends at both stuttering steps.

Write a+b when a reaches b by one or more target steps; thus + is the transitive closure of , whereas also permits zero steps.

Lemma 23.42 — Application roots have a nonempty match

Let D relate closed targets a and a. If ab contracts E-Beta or E-WrapApp, then there are b and a precision derivation relating b to b such that a+b.

Proof of Lemma 23.42 — Application roots have a nonempty match

Proof. When the left term has an application root, first peel every leading CPr-CastR rule from the precision derivation. These rules preserve the left term and record right-hand cast evaluation frames. The first remaining rule is CPr-App. For a left beta redex (λx.a)v, invert that CPr-App instance. Apply lemma 23.40 to its two premises: a1v1,a2v2. By lemma 23.12(3), v1 is a lambda or wrapper, so the right application takes E-Beta or E-WrapApp; the step lifts through all peeled right-hand cast frames. When the right value is a wrapper, its domain cast is evaluated before the inner beta redex. Endpoint precision and the related argument values give the premises of lemma 23.40; if that cast projects an injection, lemma 23.39(2) either identifies the injection already accepted by the matching left projection, or, with the cast endpoint premise, provides a common precise type below the stored and requested ground tags. In the latter case they are equal by lemma 23.38(1). It therefore reaches a value rather than taking E-Mismatch. Formally, induct on the number of leading right-hand wrapper values. The invariant is that the current arguments are related closed values and that the residual applications are related by the arrow-precision premises. One E-WrapApp exposes a domain cast; value catch-up reduces it to a value, ground-tag equality excludes E-Mismatch, and the result-cast premise re-establishes the invariant with one fewer leading wrapper. At wrapper count zero, the related function is a lambda and E-Beta applies. For a left E-WrapApp root, invert the two arrow-precision premises to relate the complementary domain casts and the result casts. Catch up the two argument values, use tag equality at every ground projection, and contract the related right wrapper root. Related substitution then relates the eventual beta residuals, so the complete right path is nonempty. ◻

Lemma 23.43 — One-step simulation with accounted stuttering

Suppose D derives a:ACa:A and ab. Then there are b and a derivation D of b:ACb:A such that either a+b, or b=a and st(b)<st(a).

Proof of Lemma 23.43 — One-step simulation with accounted stuttering

Proof. Frame lifting. Induct on D, strengthening the claim to a contraction under related evaluation frames. The induction hypothesis relates the contracted subterms, and lemma 23.37 plugs them into the frames.

Beta. At E-Beta, apply lemma 23.42; its construction includes catch-up for the function and argument, safe traversal of any right wrappers, and related substitution at the final beta root.

Wrapper application. For a left wrapper application, the root equation is (A2B2A1B1pv)wB2B1p(v(A1A2p¯w)). First peel every leading CPr-CastR rule, then invert CPr-App and apply lemma 23.40. This derives a right reduction to an application whose function is a related value. By lemma 23.12(3), that function is a lambda or wrapper, so the right takes E-Beta or E-WrapApp. Inversion of Pr-Arr gives the endpoint precision for the domain casts at the complementary labels and the result casts at the original labels; CPr-App relates the inner applications. For every domain cast introduced by a leading wrapper, endpoint precision and the related arguments invoke lemma 23.40; if a projection is reached, lemma 23.39(2) and lemma 23.38(1), with the already matching projection on the other side in the injection alternative, rule out E-Mismatch. Thus the finite sequence of leading wrappers reaches a lambda or wrapper root, and the peeled right-hand cast evaluation frames lift that nonempty sequence.

Active casts. For an active cast TSpv, lemma 23.40 derives a right reduction to an operand value related to v. For aligned identity casts, E-IdBase or E-IdUnk reduces both casts to those related operands. The E-Ground and E-Expand conclusions are the two nested casts in definition 23.6; inversion of Pr-Arr derives precision of their component endpoints. A matching projection reduces to its stored payload on each side, and lemma 23.39(1) proves the two stored tags equal. A one-sided projection instead uses clause 2 together with its endpoint-precision premise and lemma 23.38(1); when clause 2 exposes the other injection directly, the matching left root already fixes its tag. A left mismatch reduces to blame, and target regularity gives the right typing premise of CPr-Blame. If only one side retains an outer cast, CPr-CastL or CPr-CastR keeps that cast explicit.

Zero-step descent. Suppose the construction above takes zero right steps. Application roots use the nonempty branch by lemma 23.42; moreover, the reduction relation has no focus-shifting root from a completed function position to its argument. The remaining constructed zero-step cases and their changes in total cast weight are therefore the following rows:

left redex cast-weight replacement
identity 10
projection or mismatch decrease of at least 2 to 0
grounding or expansion 31+1
E-Blame discarded frame and contents

Identity gives the representative stutter calculation cw(SSpv)=1+cw(v) and cw(v), hence 10. Grounding and expansion give 31+1; projection and mismatch remove at least two descriptor units. A contraction inside an aligned or unmatched cast leaves all other contributions fixed. For E-Blame, replacing F[blamep] by blamep either discards a cast or, when no cast occurs in F, strictly decreases syntax size. Thus every constructed zero-step branch satisfies st(b)<st(a). ◻

Corollary 23.44 — Finite simulation

If a:ACa:A and ab, then ab for some b with b:ACb:A.

Proof of Corollary 23.44 — Finite simulation

Proof. Write the finite left path as a=a0ak=b. Starting with the given precision derivation, lemma 23.43 constructs, for every i<k, a right path aiai+1 and a derivation relating ai+1 to ai+1. Concatenation yields a=a0ak, with b=ak related to b=ak. ◻

Unknown values recover typed self-application, so the infinite clause cannot be derived from normalization.

Definition 23.45 — Divergence

Write aω when there is an infinite sequence a=a0a1a2.

Put δ:=λx:?.(???px)x,d:=???qδ. Then δ:??, d:?, and d is a ground injection value. The closed typed term δd:? has the exact cycle δd(???pd)dδd. Thus type safety holds, but strong normalization does not.

This cast representation also exposes a space cost. Let δ0=δ, and for k<4 put δk+1=????pkδk,d4=???qδ4. Every δk is a wrapper value; an arrow identity cast does not contract merely because its payload is a value. Evaluating δ4d4 passes through four E-WrapApp contractions before re-entering the self-application cycle. Each pass leaves four pending result casts ?? around the recursive computation, so after n cycles the continuation contains 4n such frames. The time and live syntax therefore grow linearly with the number of cycles in this example. Space-efficient coercions or threesomes compose adjacent casts instead; proposition 23.51 explains the observation theorem that such a replacement still owes.

Theorem 23.46 — Infinite simulation

If a:ACa:A and aω, then aω.

Proof of Theorem 23.46 — Infinite simulation

Proof. Fix the infinite left sequence and its initial precision derivation D0. Apply lemma 23.43 successively. For each stage i, choose the resulting derivation Di+1 for the next left residual. The same instance gives either a nonempty finite right segment or no right step and st(ai+1)<st(ai). Concatenate the nonempty segments in their production order. They join because the final right residual at one stage is the initial right term at the next.

Suppose this concatenation were finite. After its last step, every remaining left step would have to use the stutter alternative. The tail of the construction would then give an infinite strictly descending sequence st(aN)>st(aN+1)>st(aN+2)> in the lexicographic order on N×N, which is well founded. Hence the concatenation contains infinitely many right steps and is an infinite reduction from a. ◻

Corollary 23.47 — Operational trichotomy

For every closed well-typed target a, exactly one of the following holds: av for a value v, ablamep for a label p, or aω.

Proof of Corollary 23.47 — Operational trichotomy

Proof. Starting from a=a0, define ai+1 to be the unique term with aiai+1 whenever such a term exists, using theorem 23.15. If the sequence stops, its last term is a value or blame; otherwise it is an infinite reduction. Any two finite reductions from a agree step by step by determinism, while values and blame have no successor, so the alternatives are disjoint. ◻

Theorem 23.48 — Dynamic gradual guarantee

Let esrce be closed, let e:A, and let ea:A,ea:A be their unique elaborations; existence follows from theorem 23.29 and uniqueness from proposition 23.9. Then:

  1. if av, then av for some value v with v:ACv:A;

  2. if av, then either av with v:ACv:A, or ablamep for some p.

Proof of Theorem 23.48 — Dynamic gradual guarantee

Proof. By lemma 23.33, a:ACa:A. For clause 1, finite simulation relates a right residual to v, and value catch-up reduces that residual to the required related value.

For clause 2, apply operational trichotomy to a. If a reaches blame, the second alternative holds. If it reaches a value v, clause 1 gives a value w with aw and v:ACw:A. Since also av, determinism and terminality give w=v. The remaining trichotomy case is impossible: infinite simulation would give aω, whereas the assumed finite path to the terminal value v and determinism exclude any infinite path from a. ◻

Let e=((λy:2.y)((λx:?.x)0)i)o,e=((λy:?.y)((λx:?.x)0)i)o, with source positions i and o on both sides. We have esrce and both terms typecheck. The inner call injects a natural at ?. The precise outer binder projects it to 2 and blames; the less precise outer binder returns the dynamic natural value. Requiring equal observations would reject a correct gradual design: a newly inserted, incorrect annotation is supposed to expose a checked error. Target precision repairs the statement by ordering blame below successful less precise behavior.

Casts, contracts, and evidence

The first-order guard of section 10.8 asks whether one integer predicate holds. A base cast asks whether one stored type tag agrees with one demanded tag. These are both delayed checks, while definition 23.5, definition 23.6 also handle higher-order use. A function cast does not inspect a function once and declare success. It produces a contract-like wrapper that checks every future argument contravariantly and every result covariantly.

Definition 23.49 — Executable boundary contracts

The equations define a base guard and a function-contract action in the target. For ground G,H, put guardGq(?Hpv)=vif G=H,guardGq(?Hpv)=blameqif GH. For a function value v:A1B1, define its contract action on a value w:A2 by funconA1,B1;A2,B2p(v,w):=B2B1p(v(A1A2p¯w)). These are meta-level abbreviations for the target terms on the right-hand sides; their reductions are those of definition 23.6.

Proposition 23.50 — Casts execute the contract translation

For closed well-typed values at the stated endpoints:

  1. G?q(?Hpv) reduces in one step to guardGq(?Hpv);

  2. the function contraction is (A2B2A1B1pv)wfunconA1,B1;A2,B2p(v,w).

Proof of Proposition 23.50 — Casts execute the contract translation

Proof. The two cases of clause 1 are E-Project and E-Mismatch. Clause 2 is E-WrapApp; its domain label is p¯ and its result label is p, exactly as in the definition. The reduction in example 23.25 therefore executes this function contract. ◻

For the next proposition only, extend the target with a meta-observation iswrap. On a function-wrapper value it returns true; on every other target value it returns false. This operation is absent from definition 23.5. It is not part of the chapter’s value, blame, and divergence observation.

Proposition 23.51 — A coercion optimization needs an observation theorem

Here a coercion is a compiled representation that combines a sequence of casts into one run-time object while preserving their checks and blame labels. Let an optimized representation erase the arrow identity cast ABABpv to v. Both terms are values in the base target, but the extended observation distinguishes them: iswrap(v)=false,iswrap(ABABpv)=true, provided v itself is not a wrapper. Therefore identity-cast erasure is not observation preserving for the extended language.

Proof of Proposition 23.51 — A coercion optimization needs an observation theorem

Proof. The arrow cast is a wrapper value by the value grammar, whereas the chosen payload v is not. The two defining clauses of iswrap give the displayed Boolean results. A language exposing wrapper identity or allocation therefore needs a new observation theorem. ◻

The descriptor BAp records the endpoint types and owner of one run-time boundary. Ground decomposition turns that descriptor into a finite tag and, at arrow type, a wrapper. Target typing proves that the descriptor is applied at type A and returns at type B; label safety and the gradual guarantee are theorem 23.23, theorem 23.48.

Definition 23.8 inserts one cast at each consistency or matching use. An elaborator that merges adjacent casts must give a translation from these target terms and prove preservation of the chosen value-or-blame observation. The iswrap calculation shows why wrapper identity cannot be added to that observation without changing the theorem.

Proposition 23.52 — Static fragments cross without run-time failure

If a closed source term contains no ? in any annotation, its elaboration contains only casts between equal types. It cannot reduce to blame. Define partial erasure on nonblame target terms homomorphically by |b|=b,|n|=n,|x|=x,|λx:A.a|=λx:A.|a|,|a1a2|=|a1||a2|,|BApa|=|a|. It is undefined on blame. Let the cast-free call-by-value relation be the restriction of definition 23.6 to constants, lambdas, and applications; it is the discipline of definition 2.21, presented there by congruence rules rather than evaluation contexts, and extended here with numerals, which are values with no contraction of their own. Then each target reduction is either an erasure stutter |a|=|a| or one ordinary call-by-value step |a||a| of the same simply typed term.

Proof of Proposition 23.52 — Static fragments cross without run-time failure

Proof. Induct on source typing. Matching a static arrow returns that same arrow, and consistency between static types in G-App inverts to structural equality. Hence both casts inserted by I-App have equal endpoints. Base identity casts reduce by E-IdBase; arrow identity casts are wrappers whose application inserts smaller equal-endpoint casts. Induction on the arrow type eliminates those wrappers during application. No cast from ? to a ground type occurs, so E-Mismatch is unreachable. Identity-cast and wrapper-administration steps preserve erasure; E-Beta maps to the corresponding source beta step. Thus removing the stutters leaves exactly the ordinary call-by-value reduction of the simply typed source. ◻

Comparison card: dependent interoperability

The preceding calculus relates more and less precise simple types. The following card instead relates a simply typed component to a dependently typed component. It is a separate language and contributes no rule to definition 23.1, definition 23.5.

Definition 23.53 — The SD source/target card

In the SD calculus of Osera, Sjöberg, and Zdancewic, write s:S for a term of the simply typed sublanguage and t:T for a term of the dependently typed sublanguage. The compatibility judgment ST permits the explicit boundaries SDST(t):S,DSTS(s):T. Both sublanguages use call-by-value contexts, and those contexts descend into their boundary operands.

Suppose a constructor has the paired declarations C:S1A,C:(y:T1)Bt1, and constructor-indexed marshalling functions satisfy argToSC(v)=u and argToDC(u)=v. The constructor roots are SDABt(Cv)SDCu, and DSBtA(Cu)SD(t=~[v/y]t1)Cv. Here SD denotes the comparison calculus’s one-step reduction, and t=~tq is the dependent guard: it reduces to q when the closed first-order indices are equal and to error when they differ. Thus the dependent-to-simple direction forgets index evidence, whereas the simple-to-dependent direction reconstructs an index and checks it against the demanded result type.

Theorem 23.54 — Imported safety boundary for SD

Assume the paired constructor signature and compatibility rules of SD, and assume its user-supplied argToS, argToD, and constructor-correlation operations satisfy Properties 1–5 of Figure 8: typing, constructor agreement, substitution compatibility, parallel-reduction compatibility, and definedness on closed values. Then SD reduction preserves simple typing and the dependent typing and kinding instances stated in Theorem 1. Every closed well-typed simple or dependent term either steps, is a value, or is error, as in Theorem 2.

Proof of Theorem 23.54 — Imported safety boundary for SD

Proof. This is Osera, Sjöberg, and Zdancewic’s Preservation and Progress theorems, Theorems 1 and 2 on PDF pp. 7–8 [OSZ12]. The hypotheses above are the conversion-function obligations printed in their Figure 8. They are essential: without defined marshalling on closed values, a boundary may be stuck; without the typing and compatibility obligations, its translated constructor or guard need not preserve the demanded type. ◻

Dagand, Tabareau, and Tanter replace these ad hoc pairs by partial Galois connections. Exact connections give checks sound and complete relative to the represented invariant; anticonnections preserve soundness while deliberately giving up completeness [DTT18]. That comparison is not a blame theorem and does not strengthen theorem 23.54.

The boundary of the result

Four distinctions carry the chapter.

  1. Consistency is a symmetric, non-transitive permission to insert a cast; it is neither equality nor subtyping.

  2. Type safety classifies blame as a result. Blame safety then excludes one face under an explicit positive or negative hypothesis.

  3. Precision is covariant through both parts of a function type because it replaces annotations by unknowns. By contrast, the behavioral arrow subtyping rule of chapter 8 is contravariant in its domain and covariant in its codomain because it orders values by substitutability.

  4. The dynamic gradual guarantee is an error approximation. Removing annotations preserves successful precise behavior; adding annotations may expose blame, but may not silently produce an unrelated value.

References, structural type tests, polymorphism, effects, and dependent indices add new run-time observations or evidence, so each requires its own gradual-guarantee theorem.

Sources.

Siek and Taha define consistency, matching, and cast insertion. Their Proposition 1 and Theorem 1 are on PDF p. 3; Lemmas 2–4 on PDF p. 6 give unique typing, typed insertion, and the static fragment; Lemma 5 on PDF p. 7 and Lemma 8 and Theorem 2 on PDF p. 9 give safety [ST06]. The source judgments in definition 23.1 instantiate those mechanisms for base types, arrows, and ?.

Wadler and Findler’s rules are in Figure 3, PDF p. 5. Lemmas 4–5 and Propositions 6–7 on PDF p. 8 give substitution, canonical forms, preservation, and progress; Propositions 8–10 and Corollary 11 on PDF pp. 8–9 give positive and negative blame [WF09]. Their calculus includes subset types. The subset-free target fixed in definition 23.5 uses the same wrapper polarity, and theorem 23.14, theorem 23.23 prove its local preservation and blame claims.

Siek, Vitousek, Cimini, and Boyland give precision in Figure 6, the gradual guarantee in Theorem 5 on PDF p. 11 (printed p. 284), Lemmas 6–8 on PDF p. 15 (printed p. 288), and Lemmas 9–11 on PDF p. 16 (printed p. 289) [SVCB15]; their counterexamples are on printed pp. 285–287. The precision relation in definition 23.30 specializes that proof structure to the ground-cast target, and theorem 23.48 states the resulting value, blame, and divergence alternatives for this signature.

Osera, Sjöberg, and Zdancewic’s boundary syntax is in Figures 3–6, PDF pp. 3–6, its conversion-function premises are in Figure 8 on PDF p. 7, and its Preservation and Progress results are Theorems 1–2 on PDF pp. 7–8 [OSZ12]. The partial-Galois connection comparison is from [DTT18].

Suggested first pass.

Begin with exercise 23.9, exercise 23.10, exercise 23.12, exercise 23.14; then use the remaining problems to reconstruct the guarantee and test blame polarity.

Exercise 23.9

★★☆ Let i=λn:N.n and w=??NNpi. Derive i:NNCw:?? and apply the two functions to the related arguments 0:N and z=?Nr0:?. Reduce wz completely. Exhibit the domain cast N?p¯ and result cast ?Np, and derive the target precision between the two final values.

Exercise 23.10

★★☆ Reprove the E-WrapApp case of preservation as a complete typing tree. The domain premise must use p¯ and the result premise must use p. Then show exactly which premise is lost if the domain cast is incorrectly oriented as A2A1p¯.

Exercise 23.11

★★☆ Explain why clause 2 of theorem 23.48 permits blame but clause 1 does not. Give a hypothetical reverse clause that omits blame and refute it with the displayed, fully labeled esrce. Finally, identify the exact place where theorem 23.46, rather than a normalization theorem, excludes divergence in the proof of clause 2.

Exercise 23.12

★★☆ Elaborate both terms in the dynamic-guarantee counterexample and reduce them to their distinct results. Give the result types and identify the instance of CPr-Blame that relates those results.

Exercise 23.13

★★☆ Replace p¯ by p in E-WrapApp. Reduce the first program of example 23.25. Which face is blamed? State the exact line of lemma 23.22 that is then false.

Exercise 23.14

★★☆ For ((λf:NN.(f0)i)(λn:N.n))o, write its elaboration, including arrow identity wrappers. Interleave target reduction with erasure and verify the simulation claimed in proposition 23.52.

Exercise 23.15

★★★ Practical project.gradual-cast-simulator Build and run an elaborator and finite cast machine. Preserve the invariant that a function wrapper reverses the domain blame face and preserves the result face. The five-case trace must end with All 5 gradual-cast corpus cases passed., and the audit must be empty. Test three unsound variants that alter domain polarity, omit ground-tag checking, or discard application labels; the five-case oracle must reject each variant. The result is a finite blame oracle, not a mechanized dynamic gradual guarantee.

Search the book

Type to search the local edition.