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 𝖳𝗈𝗉 from chapter 8 does not help: a value at 𝖳𝗈𝗉 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 𝐴,𝐵,𝐶 for source and target types.
Let 𝑏 range over 𝗍𝗋𝗎𝖾,𝖿𝖺𝗅𝗌𝖾 and let 𝑛 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 ?: 𝐴,𝐵::=𝟐∣ℕ∣?∣𝐴→𝐵,𝑒::=𝑏∣𝑛∣𝑥∣𝜆𝑥:𝐴.𝑒∣(𝑒1𝑒2)ℓ. Arrows associate to the right, so 𝐴→𝐵→𝐶 abbreviates 𝐴→(𝐵→𝐶). 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𝐴∼𝖼𝐵 is the least relation generated by
?∼𝖼𝐴
C-UnkL
𝐴∼𝖼?
C-UnkR
𝟐∼𝖼𝟐
C-Bool
ℕ∼𝖼ℕ
C-Nat
𝐴1∼𝖼𝐵1𝐴2∼𝖼𝐵2
𝐴1→𝐴2∼𝖼𝐵1→𝐵2
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 ?∼𝖼? insert the same cast.
Function matching is the partial operation 𝖿𝗎𝗇(𝐴→𝐵)=𝐴→𝐵,𝖿𝗎𝗇(?)=?→?, and is undefined on 𝟐 and ℕ. Source typing is generated by the following rules.
Γ⊢𝑏:𝟐
G-Bool
Γ⊢𝑛:ℕ
G-Nat
𝑥:𝐴∈Γ
Γ⊢𝑥:𝐴
G-Var
Γ,𝑥:𝐴⊢𝑒:𝐵
Γ⊢𝜆𝑥:𝐴.𝑒:𝐴→𝐵
G-Lam
Γ⊢𝑒1:𝐶𝖿𝗎𝗇(𝐶)=𝐴→𝐵Γ⊢𝑒2:𝐷𝐷∼𝖼𝐴
Γ⊢(𝑒1𝑒2)ℓ:𝐵
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.
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, 𝟐∼𝖼?and?∼𝖼ℕ,but𝟐≁𝖼ℕ. 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 𝖺𝗉𝗉𝗅𝗒:=𝜆𝑓:?.𝜆𝑥:?.(𝑓𝑥)ℓ0. Matching gives 𝑓 the temporary shape ?→?, and the declared type of 𝑥 satisfies ?∼𝖼?, so 𝑓:?,𝑥:?⊢𝑓:?𝖿𝗎𝗇(?)=?→?𝑓:?,𝑥:?⊢𝑥:??∼𝖼?𝑓:?,𝑥:?⊢(𝑓𝑥)ℓ0:?G−App𝑓:?⊢𝜆𝑥:?.(𝑓𝑥)ℓ0:?→?G−Lam⋅⊢𝖺𝗉𝗉𝗅𝗒:?→?→?G−Lam. The derivation authorizes a run-time check at ℓ0; the target cast decides whether the actual argument tag matches the function’s domain.
Proof. Induct on the syntax of 𝑒. 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 𝐶 of the function position. The partial function 𝖿𝗎𝗇 has at most one result, so its codomain 𝐵 is fixed. The consistency premise checks the argument but does not choose the result. ◻
★★☆ Give derivations of 𝟐∼𝖼? and ?∼𝖼ℕ. Prove by inversion that 𝟐≁𝖼ℕ. Then attempt to prove transitivity of ∼𝖼 by induction on its first derivation and identify the case in which the required second derivation has no invertible outer constructor.
★☆☆ Derive the type of ((𝜆𝑔:?.(𝑔0)ℓ1)(𝜆𝑛:ℕ.𝑛))ℓ2. Then replace the last binder annotation by 𝟐. Does source typing reject the program, or does it postpone the mismatch? Name the premise responsible.
The cast calculus executes labeled boundaries. A blame name has two faces, 𝑝 and ¯𝑝, with ――¯𝑝=𝑝. In a boundary cast, 𝑝 names the provider of the value and ¯𝑝 names its context.
Ground types are the run-time tags: 𝐺::=𝟐∣ℕ∣?→?. For 𝐴≠?, define its ground shape by 𝗀𝗇𝖽(𝟐)=𝟐,𝗀𝗇𝖽(ℕ)=ℕ,𝗀𝗇𝖽(𝐴→𝐵)=?→?. Target terms are 𝑎::=𝑏∣𝑛∣𝑥∣𝜆𝑥:𝐴.𝑎∣𝑎1𝑎2∣⟨𝐵⇐𝐴⟩𝑝𝑎∣𝖻𝗅𝖺𝗆𝖾𝑝.⟨𝐵⇐𝐴⟩𝑝𝑎 checks a value already typed at 𝐴 and returns it at 𝐵, or produces blame 𝑝. We write Γ⊢𝐶𝑎:𝐴 for typing in this cast calculus, to distinguish it from source typing Γ⊢𝑒:𝐴.
Γ⊢𝐶𝑏:𝟐
T-Bool
Γ⊢𝐶𝑛:ℕ
T-Nat
𝑥:𝐴∈Γ
Γ⊢𝐶𝑥:𝐴
T-Var
Γ,𝑥:𝐴⊢𝐶𝑎:𝐵
Γ⊢𝐶𝜆𝑥:𝐴.𝑎:𝐴→𝐵
T-Lam
Γ⊢𝐶𝑎1:𝐴→𝐵Γ⊢𝐶𝑎2:𝐴
Γ⊢𝐶𝑎1𝑎2:𝐵
T-App
Γ⊢𝐶𝑎:𝐴𝐴∼𝖼𝐵
Γ⊢𝐶⟨𝐵⇐𝐴⟩𝑝𝑎:𝐵
T-Cast
Γ⊢𝐶𝖻𝗅𝖺𝗆𝖾𝑝:𝐵
T-Blame
Here 𝐵 may be any type in the type grammar. Consequently target typing is not unique for blame: the same 𝖻𝗅𝖺𝗆𝖾𝑝 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 𝑣 and 𝑤 range over values: 𝑣::=𝑏∣𝑛∣𝜆𝑥:𝐴.𝑎∣⟨𝐴2→𝐵2⇐𝐴1→𝐵1⟩𝑝𝑣∣⟨?⇐𝐺⟩𝑝𝑣. 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 (𝐴1→𝐵1,𝐴2→𝐵2,𝑝,𝑣). On application it constructs the domain cast at ¯𝑝 and result cast at 𝑝 without consulting source syntax.
Evaluation frames and contexts are 𝐹::=[]𝑎∣𝑣[]∣⟨𝐵⇐𝐴⟩𝑞[],𝐸::=[]∣𝐹[𝐸]. Reduction is compatible with these contexts and is generated by the following contractions. In the two decomposition rules, 𝐴 is neither ? nor ground.
(𝜆𝑥:𝐴.𝑎)𝑣⟼𝑎[𝑣/𝑥]
E-Beta
𝐵∈{𝟐,ℕ}
⟨𝐵⇐𝐵⟩𝑝𝑣⟼𝑣
E-IdBase
⟨?⇐?⟩𝑝𝑣⟼𝑣
E-IdUnk
⟨𝐺⇐?⟩𝑝(⟨?⇐𝐺⟩𝑞𝑣)⟼𝑣
E-Project
𝐺1≠𝐺2
⟨𝐺2⇐?⟩𝑝(⟨?⇐𝐺1⟩𝑞𝑣)⟼𝖻𝗅𝖺𝗆𝖾𝑝
E-Mismatch
𝐴≠?𝐴≠𝗀𝗇𝖽(𝐴)
⟨?⇐𝐴⟩𝑝𝑣⟼⟨?⇐𝗀𝗇𝖽(𝐴)⟩𝑝(⟨𝗀𝗇𝖽(𝐴)⇐𝐴⟩𝑝𝑣)
E-Ground
𝐴≠?𝐴≠𝗀𝗇𝖽(𝐴)
⟨𝐴⇐?⟩𝑝𝑣⟼⟨𝐴⇐𝗀𝗇𝖽(𝐴)⟩𝑝(⟨𝗀𝗇𝖽(𝐴)⇐?⟩𝑝𝑣)
E-Expand
The function rule is the only contraction that changes polarity.
𝑢=⟨𝐴2→𝐵2⇐𝐴1→𝐵1⟩𝑝𝑣𝑤𝗏𝖺𝗅𝗎𝖾
𝑢𝑤⟼⟨𝐵2⇐𝐵1⟩𝑝(𝑣(⟨𝐴1⇐𝐴2⟩¯𝑝𝑤))
E-WrapApp
𝐹[𝖻𝗅𝖺𝗆𝖾𝑝]⟼𝖻𝗅𝖺𝗆𝖾𝑝
E-Blame
In this rule, 𝐴𝑖 is the domain and 𝐵𝑖 the codomain on side 𝑖. The value premise is load bearing: a nonvalue argument must first step in the frame 𝑢[], 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 ℕ→ℕ 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 ℕ→ℕ with a stored tag ?→ℕ: ⟨ℕ→ℕ⇐?⟩𝑞(⟨?⇐?→ℕ⟩𝑝𝑣). 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.
Let 𝑑=⟨?⇐ℕ⟩𝑝0. Then ⟨ℕ⇐?⟩𝑞𝑑⟼0 by E-Project. In contrast, ⟨𝟐⇐?⟩𝑞𝑑⟼𝖻𝗅𝖺𝗆𝖾𝑞 by E-Mismatch. The injection’s label 𝑝 is not blamed: the projection at 𝑞 made the false promise that a value tagged ℕ was a Boolean.
Define the two roots deterministically by ℓ𝑓:=(ℓ,𝖿𝗎𝗇) and ℓ𝑎:=(ℓ,𝖺𝗋𝗀), 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 ℓ𝑖,𝑓 and ℓ𝑜,𝑎 abbreviate (ℓ𝑖)𝑓 and (ℓ𝑜)𝑎. The elaboration judgment Γ⊢𝑒⇝𝑎:𝐴 has the same constant, variable, and lambda structure as source typing. Its application rule inserts exactly the checks justified by matching and consistency.
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 𝖿𝗎𝗇(𝐶)=𝐴0→𝐵and𝐷∼𝖼𝐴0.Lemma 23.2 gives 𝐶∼𝖼𝐴0→𝐵: it is equality when 𝐶 is an arrow, and follows from C-UnkL when 𝐶=?. Hence T-Cast types the function cast at 𝐴0→𝐵 and the argument cast at 𝐴0; target application yields 𝐵. All choices were fixed by the source derivation and the fresh-label convention. ◻
For a closed source term, write 𝑒⇓𝑟 when there are 𝑎,𝐴 such that ⋅⊢𝑒⇝𝑎:𝐴,𝑎⟼∗𝑟,𝑟isavalueorlabeledblame. Thus ⇓ packages the separate elaboration and target-evaluation judgments; it is not another target reduction relation.
Let 𝑖=𝜆𝑛:ℕ.𝑛, and put 𝑒0=((𝜆𝑓:?.(𝑓0)ℓ𝑖)𝑖)ℓ𝑜,𝑒𝖻=((𝜆𝑓:?.(𝑓𝗍𝗋𝗎𝖾)ℓ𝑖)𝑖)ℓ𝑜. Both source terms have type ?. To keep the target calculation readable, define 𝑧0=⟨?⇐ℕ⟩ℓ𝑖,𝑎0,𝑧𝖻=⟨?⇐𝟐⟩ℓ𝑖,𝑎𝗍𝗋𝗎𝖾,ℎ𝑧(𝑓)=(⟨?→?⇐?⟩ℓ𝑖,𝑓𝑓)𝑧,𝑔𝑧=𝜆𝑓:?.ℎ𝑧(𝑓),𝑢𝑧=⟨?→?⇐?→?⟩ℓ𝑜,𝑓𝑔𝑧,𝑑=⟨?⇐ℕ→ℕ⟩ℓ𝑜,𝑎𝑖. Here ℎ𝑧(𝑎) is meta-level substitution of 𝑎 for the written occurrence 𝑓. 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 ℓ𝑖 and then at ℓ𝑜, gives ⋅⊢𝑒0⇝𝑢𝑧0𝑑:?,⋅⊢𝑒𝖻⇝𝑢𝑧𝖻𝑑:?. First, in either program, E-Ground changes 𝑑 to the value 𝑑†:=⟨?⇐?→?⟩ℓ𝑜,𝑎(⟨?→?⇐ℕ→ℕ⟩ℓ𝑜,𝑎𝑖). For 𝑧∈{𝑧0,𝑧𝖻}, the common prefix is 𝑢𝑧𝑑†𝐸−𝑊𝑟𝑎𝑝𝐴𝑝𝑝⟼⟨?⇐?⟩ℓ𝑜,𝑓(𝑔𝑧(⟨?⇐?⟩¯ℓ𝑜,𝑓𝑑†))𝐸−𝐼𝑑𝑈𝑛𝑘⟼⟨?⇐?⟩ℓ𝑜,𝑓(𝑔𝑧𝑑†)𝐸−𝐵𝑒𝑡𝑎⟼⟨?⇐?⟩ℓ𝑜,𝑓ℎ𝑧(𝑑†)𝐸−𝑃𝑟𝑜𝑗𝑒𝑐𝑡⟼⟨?⇐?⟩ℓ𝑜,𝑓(𝑤𝑧), Its ground tag already is ?→?, so E-Expand does not apply. The projected payload itself is the wrapper value 𝑤:=⟨?→?⇐ℕ→ℕ⟩ℓ𝑜,𝑎𝑖. Applying this wrapper gives 𝑤𝑧𝐸−𝑊𝑟𝑎𝑝𝐴𝑝𝑝⟼⟨?⇐ℕ⟩ℓ𝑜,𝑎(𝑖(⟨ℕ⇐?⟩¯ℓ𝑜,𝑎𝑧)).E-WrapApp casts the argument from ? to ℕ at ¯ℓ𝑜,𝑎 before applying 𝑖. A natural tag projects to 0; a Boolean tag produces 𝖻𝗅𝖺𝗆𝖾¯ℓ𝑜,𝑎. For 𝑧=𝑧0, the remaining roots yield 𝑢𝑧0𝑑𝐸−𝑃𝑟𝑜𝑗𝑒𝑐𝑡,𝐸−𝐵𝑒𝑡𝑎,𝐸−𝐼𝑑𝑈𝑛𝑘⟼∗⟨?⇐ℕ⟩ℓ𝑜,𝑎0. For 𝑧=𝑧𝖻, mismatch and blame propagation instead give 𝑢𝑧𝖻𝑑𝐸−𝑀𝑖𝑠𝑚𝑎𝑡𝑐ℎ,𝑡ℎ𝑒𝑛𝐸−𝐵𝑙𝑎𝑚𝑒⟼∗𝖻𝗅𝖺𝗆𝖾¯ℓ𝑜,𝑎. The two source-to-result outcomes are 𝑒0⇓⟨?⇐ℕ⟩ℓ𝑜,𝑎0,𝑒𝖻⇓𝖻𝗅𝖺𝗆𝖾¯ℓ𝑜,𝑎. 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.
★★☆ Write the complete target elaborations of ((𝜆𝑥:?.𝑥)0)ℓ1and((𝜆𝑥:𝟐.𝑥)((𝜆𝑦:?.𝑦)0)ℓ2)ℓ3. Keep every identity cast and give every generated cast its indicated fresh function or argument label.
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.
Proof. Induct on the typing derivation of 𝑎. 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. ◻
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. ◻
Proof of Lemma 23.13 — A cast on a value progresses
Proof. Analyze 𝐴 and 𝐵. If both are the same base type, use E-IdBase; if both are ?, use E-IdUnk. If 𝐴 and 𝐵 are arrows, the term is a wrapper value. If 𝐵=? and 𝐴 is ground, it is an injection value; if 𝐴 is a non-ground arrow, use E-Ground. It remains that 𝐴=? and 𝐵≠?. If 𝐵 is non-ground, use E-Expand. If 𝐵=𝐺2 is ground, canonical forms writes 𝑣=⟨?⇐𝐺1⟩𝑞𝑤. Exactly one of 𝐺1=𝐺2 and 𝐺1≠𝐺2 holds, so exactly one of E-Project and E-Mismatch applies. Consistency excludes all remaining base–base and base–arrow pairs. ◻
★★☆ Suppose E-Ground injected an arbitrary non-ground type directly, so that every 𝐴 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.
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 𝐴∼𝖼𝗀𝗇𝖽(𝐴) and 𝗀𝗇𝖽(𝐴)∼𝖼?. Two applications of T-Cast therefore yield the target type ?. The dual argument handles E-Expand.
For the load-bearing wrapper case, inversion gives 𝑣:𝐴1→𝐵1,𝑤:𝐴2,𝐴1∼𝖼𝐴2,𝐵1∼𝖼𝐵2. The domain cast labeled ¯𝑝 has type 𝐴1, so 𝑣(⟨𝐴1⇐𝐴2⟩¯𝑝𝑤):𝐵1. The cast is well formed because lemma 23.3 changes 𝐴1∼𝖼𝐴2 into 𝐴2∼𝖼𝐴1. The result cast labeled 𝑝 then has type 𝐵2, 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. ◻
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 []𝑎 frame; once the operator is a value, a nonvalue argument has only the 𝑣[] 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. ◻
If ⋅⊢𝑒⇝𝑎:𝐴, then every finite reduction of 𝑎 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 𝐴. 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.
The judgments 𝐴⪯+𝐵 and 𝐴⪯−𝐵 are the least relations generated by the following rules.
𝟐⪯+𝟐
P-Bool
ℕ⪯+ℕ
P-Nat
𝐴⪯+?
P-Unk
𝐵1⪯−𝐴1𝐴2⪯+𝐵2
𝐴1→𝐴2⪯+𝐵1→𝐵2
P-Arr
𝟐⪯−𝟐
N-Bool
ℕ⪯−ℕ
N-Nat
?⪯−𝐴
N-Unk
𝐺isground𝐴⪯−𝐺
𝐴⪯−?
N-GroundUnk
𝐵1⪯+𝐴1𝐴2⪯−𝐵2
𝐴1→𝐴2⪯−𝐵1→𝐵2
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 𝐺∈{𝟐,ℕ,?→?} 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: ℕ→ℕ⪯+?by𝑃−𝑈𝑛𝑘,ℕ→ℕ⪯̸−?. For the missing negative derivation, N-GroundUnk would require a ground 𝐺 with ℕ→ℕ⪯−𝐺. The only possible arrow-shaped ground tag is 𝐺=?→?, but N-Arr would then require ?⪯+ℕ, for which there is no rule.
Proof. Proceed simultaneously by induction on 𝐴. The base cases are P-Bool, N-Bool, P-Nat, and N-Nat. At ? use P-Unk and N-Unk. For 𝐴=𝐴1→𝐴2, apply P-Arr to the negative induction hypothesis on 𝐴1 and the positive hypothesis on 𝐴2; apply N-Arr to the positive hypothesis on 𝐴1 and the negative hypothesis on 𝐴2. ◻
Proof of Lemma 23.20 — Grounding preserves polar safety
Proof. If 𝐴 is a base type, then 𝗀𝗇𝖽(𝐴)=𝐴. Clauses 1 and 3 are therefore polar reflexivity followed or preceded by P-Unk or N-Unk. Clause 2 assumes that 𝐴 is not ground, so it has no base case.
Let 𝐴=𝐴1→𝐴2. Then 𝗀𝗇𝖽(𝐴)=?→?. For clause 1, P-Arr uses ?⪯−𝐴1,𝐴2⪯+?, from N-Unk and P-Unk; another P-Unk relates the ground arrow to ?. For clause 2, inversion of N-GroundUnk gives 𝐴⪯−𝐺. Arrow inversion and groundness force 𝐺=?→?=𝗀𝗇𝖽(𝐴); polar reflexivity and N-GroundUnk give the second judgment. For clause 3, N-Unk gives ?⪯−𝗀𝗇𝖽(𝐴), and N-Arr uses 𝐴1⪯+? and ?⪯−𝐴2 for the remaining judgment. ◻
Fix one face 𝑞. A target term is 𝑞-safe when it contains no occurrence of 𝖻𝗅𝖺𝗆𝖾𝑞, every cast carrying 𝑞 has source 𝑆 and target 𝑇 with 𝑆⪯+𝑇, and every cast carrying ¯𝑞 has 𝑆⪯−𝑇. Casts with unrelated labels are unrestricted.
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 ?⪯+𝐴 for non-ground 𝐴, and no rule derives that judgment.
For E-WrapApp, first suppose its label is 𝑞. Positive safety of 𝐴1→𝐵1⪯+𝐴2→𝐵2 inverts to 𝐴2⪯−𝐴1 and 𝐵1⪯+𝐵2. These are exactly the safety obligations for the new domain cast labeled ¯𝑞 and result cast labeled 𝑞. If the wrapper label is ¯𝑞, negative safety inverts to 𝐴2⪯+𝐴1 and 𝐵1⪯−𝐵2, again exactly matching the swapped labels.
Only E-Mismatch creates blame directly. A mismatch that produced 𝖻𝗅𝖺𝗆𝖾𝑞 would have outer cast ⟨𝐺⇐?⟩𝑞. But ?⪯+𝐺 has no derivation for ground 𝐺; this contradicts 𝑞-safety. A mismatch labeled ¯𝑞 may instead create 𝖻𝗅𝖺𝗆𝖾¯𝑞, which is not excluded. A 𝑞-safe term contains no 𝖻𝗅𝖺𝗆𝖾𝑞, so E-Blame can propagate only an unrelated or complementary blame. Context closure preserves these observations. ◻
Proof of Theorem 23.23 — Positive and negative blame
Proof. For the first sentence, induct on the length of a purported reduction to 𝖻𝗅𝖺𝗆𝖾𝑞. Length zero is excluded by the definition of 𝑞-safety. At positive length, repeated use of lemma 23.22 says every predecessor is 𝑞-safe, but the lemma excludes the final contraction that first creates 𝖻𝗅𝖺𝗆𝖾𝑞. Blame propagation cannot be first: its premise already contains that blame.
For clause 1, the only 𝑝-labeled cast has positive-safe endpoints and there is no ¯𝑝-labeled cast initially, so 𝑎 is 𝑝-safe. For clause 2, instantiate the first sentence with 𝑞:=¯𝑝. Since ――¯𝑝=𝑝, the original 𝑝-labeled cast is exactly a cast carrying ¯𝑞; its negative-safe endpoints satisfy the definition of ¯𝑝-safety for 𝑎. Apply the first sentence. ◻
Proof of Corollary 23.24 — Ownership at a typed–dynamic boundary
Proof. Rule P-Unk gives 𝐴⪯+? in the first clause, so theorem 23.23 excludes 𝖻𝗅𝖺𝗆𝖾𝑝. Rule N-Unk gives ?⪯−𝐴 in the second clause, so the same theorem excludes 𝖻𝗅𝖺𝗆𝖾¯𝑝. 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 ℓ𝑜,𝑎. The successful call returns a tagged natural. The Boolean call fails at ¯ℓ𝑜,𝑎, 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.
Let 𝑖=𝜆𝑛:ℕ.𝑛 and let 𝑑=⟨?⇐𝟐⟩𝑟𝗍𝗋𝗎𝖾. The typed function is exposed at the canonical dynamic function type and then called by a dynamic context: (⟨?→?⇐ℕ→ℕ⟩𝑝𝑖)𝑑. Rule E-WrapApp gives ⟨?⇐ℕ⟩𝑝(𝑖(⟨ℕ⇐?⟩¯𝑝𝑑)). The inner projection sees a 𝟐 tag and reduces to 𝖻𝗅𝖺𝗆𝖾¯𝑝; propagation returns that blame. The cast’s codomain kept 𝑝, but its domain used ¯𝑝. Since ℕ→ℕ⪯+?→?, the provider 𝑝 could not be blamed. The dynamic context supplied the bad argument and receives the complement.
In the other direction let 𝑘=𝜆𝑥:?.⟨?⇐𝟐⟩𝑟𝗍𝗋𝗎𝖾, a target value of type ?→?, and cast it to ℕ→ℕ. Application to 0 checks the argument successfully, but the result is tagged 𝟐 and its projection to ℕ produces 𝖻𝗅𝖺𝗆𝖾𝑞. Because ?→?⪯−ℕ→ℕ, the typed context face ¯𝑞 is protected; the dynamic provider face 𝑞 is not.
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: 𝐴⊑𝗍𝗒𝐵meansthatAismoreprecisethanB. It is not the domain-approximation order ⊑𝖣 used for recursive types; no precision fact crosses between the two.
Contexts are related when they have the same variables in the same order and corresponding declared types are related:
⋅⊑𝖼𝗍𝗑⋅
PrCtx-Empty
Γ⊑𝖼𝗍𝗑Γ′𝐴⊑𝗍𝗒𝐴′
Γ,𝑥:𝐴⊑𝖼𝗍𝗑Γ′,𝑥:𝐴′
PrCtx-Extend
Source-term precision is the least compatible relation generated by
𝑏⊑𝗌𝗋𝖼𝑏
PrTm-Bool
𝑛⊑𝗌𝗋𝖼𝑛
PrTm-Nat
𝑥⊑𝗌𝗋𝖼𝑥
PrTm-Var
𝐴⊑𝗍𝗒𝐴′𝑒⊑𝗌𝗋𝖼𝑒′
𝜆𝑥:𝐴.𝑒⊑𝗌𝗋𝖼𝜆𝑥:𝐴′.𝑒′
PrTm-Lam
𝑒1⊑𝗌𝗋𝖼𝑒′1𝑒2⊑𝗌𝗋𝖼𝑒′2
(𝑒1𝑒2)ℓ⊑𝗌𝗋𝖼(𝑒′1𝑒′2)ℓ
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.
Proof of Proposition 23.27 — Precision factors through polar safety
Proof. For the forward implication, induct on 𝐴⊑𝗍𝗒𝐵. At 𝐵=? 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 𝐵. If 𝐵=?, conclude by Pr-Unk. If 𝐵 is a base type, inversion of 𝐴⪯+𝐵 forces the same base type, so use Pr-Bool or Pr-Nat. If 𝐵=𝐵1→𝐵2, inversion of the positive judgment forces 𝐴=𝐴1→𝐴2. Inverting both polar judgments gives 𝐴1⪯+𝐵1,𝐵1⪯−𝐴1,𝐴2⪯+𝐵2,𝐵2⪯−𝐴2. The induction hypotheses and Pr-Arr finish the proof. ◻
Proof of Lemma 23.28 — Matching and consistency lose information monotonically
Proof. First invert precision. If 𝐴′ is an arrow, its two component premises give the result. If 𝐴′=?, then 𝖿𝗎𝗇(𝐴′)=?→?, and both components of the precise arrow are more precise than ?. No other 𝐴′ 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 𝐷∼𝖼𝐵: if 𝐷⊑𝗍𝗒𝐷′ and 𝐵⊑𝗍𝗒𝐵′, then 𝐷′∼𝖼𝐵′. For C-UnkL, precision from ? forces 𝐷′=?, 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 𝖿𝗎𝗇(𝐴′)=𝐵′→𝐶′, applying the auxiliary statement to 𝐷∼𝖼𝐵, 𝐷⊑𝗍𝗒𝐷′, and 𝐵⊑𝗍𝗒𝐵′ derives 𝐷′∼𝖼𝐵′. ◻
Proof. Induct on the derivation of 𝑒⊑𝗌𝗋𝖼𝑒′ while inverting the typing of 𝑒. 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 Γ⊢𝑒1:𝐶,𝖿𝗎𝗇(𝐶)=𝐵→𝐴,Γ⊢𝑒2:𝐷,𝐷∼𝖼𝐵. The two induction hypotheses give 𝐶⊑𝗍𝗒𝐶′ and 𝐷⊑𝗍𝗒𝐷′ together with typings of the less precise subterms. Apply lemma 23.28 with its variables (𝐴,𝐴′,𝐵,𝐶,𝐷,𝐷′) instantiated as (𝐶,𝐶′,𝐵,𝐴,𝐷,𝐷′). The lemma gives 𝖿𝗎𝗇(𝐶′)=𝐵′→𝐴′ with 𝐵⊑𝗍𝗒𝐵′ and 𝐴⊑𝗍𝗒𝐴′, as well as 𝐷′∼𝖼𝐵′. Rule G-App therefore types the less precise application at 𝐴′. ◻
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 𝑟=((𝜆𝑥:ℕ.𝑥)𝗍𝗋𝗎𝖾)ℓ,𝑟′=((𝜆𝑥:?.𝑥)𝗍𝗋𝗎𝖾)ℓ. Then 𝑟⊑𝗌𝗋𝖼𝑟′ and ⋅⊢𝑟′:?, but 𝑟 has no type: its application premise would require 𝟐∼𝖼ℕ. Thus a reverse static guarantee cannot start merely from the typing of the less precise program.
★★☆ 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.
★★☆ Write the complete typing derivation for 𝑟′ and invert a hypothetical typing derivation for 𝑟 until it requires 𝟐∼𝖼ℕ. Which direction of theorem 23.29 remains valid?
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.
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 Δ=(𝑥1:𝐴1⊑𝗍𝗒𝐴′1,…,𝑥𝑛:𝐴𝑛⊑𝗍𝗒𝐴′𝑛). Its left and right projections are Δ𝐿=(𝑥1:𝐴1,…,𝑥𝑛:𝐴𝑛) and Δ𝑅=(𝑥1:𝐴′1,…,𝑥𝑛:𝐴′𝑛). For an ordinary context Γ=(𝑥1:𝐴1,…,𝑥𝑛:𝐴𝑛), write ΔΓ:=(𝑥1:𝐴1⊑𝗍𝗒𝐴1,…,𝑥𝑛:𝐴𝑛⊑𝗍𝗒𝐴𝑛) for its diagonal precision context. The judgment Δ⊢𝑎:𝐴⊑𝖢𝑎′:𝐴′ is the least relation generated by the following rules.
Δ⊢𝑏:𝟐⊑𝖢𝑏:𝟐
CPr-Bool
Δ⊢𝑛:ℕ⊑𝖢𝑛:ℕ
CPr-Nat
𝑥:𝐴⊑𝗍𝗒𝐴′∈Δ
Δ⊢𝑥:𝐴⊑𝖢𝑥:𝐴′
CPr-Var
𝐴⊑𝗍𝗒𝐴′Δ,𝑥:𝐴⊑𝗍𝗒𝐴′⊢𝑎:𝐵⊑𝖢𝑎′:𝐵′
Δ⊢𝜆𝑥:𝐴.𝑎:𝐴→𝐵⊑𝖢𝜆𝑥:𝐴′.𝑎′:𝐴′→𝐵′
CPr-Lam
Δ⊢𝑎1:𝐴→𝐵⊑𝖢𝑎′1:𝐴′→𝐵′Δ⊢𝑎2:𝐴⊑𝖢𝑎′2:𝐴′
Δ⊢𝑎1𝑎2:𝐵⊑𝖢𝑎′1𝑎′2:𝐵′
CPr-App
Aligned casts retain the same boundary name:
Δ⊢𝑎:𝑆⊑𝖢𝑎′:𝑆′𝑆∼𝖼𝑇𝑆′∼𝖼𝑇′𝑇⊑𝗍𝗒𝑇′
Δ⊢⟨𝑇⇐𝑆⟩𝑝𝑎:𝑇⊑𝖢⟨𝑇′⇐𝑆′⟩𝑝𝑎′:𝑇′
CPr-Cast
Reduction can decompose a cast on only one side:
Δ⊢𝑎:𝑆⊑𝖢𝑎′:𝑈𝑆∼𝖼𝑇𝑇⊑𝗍𝗒𝑈
Δ⊢⟨𝑇⇐𝑆⟩𝑝𝑎:𝑇⊑𝖢𝑎′:𝑈
CPr-CastL
Δ⊢𝑎:𝑆⊑𝖢𝑎′:𝑈𝑈∼𝖼𝑉𝑆⊑𝗍𝗒𝑉
Δ⊢𝑎:𝑆⊑𝖢⟨𝑉⇐𝑈⟩𝑝𝑎′:𝑉
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:
Δ𝑅⊢𝐶𝑎′:𝐴′𝐴⊑𝗍𝗒𝐴′
Δ⊢𝖻𝗅𝖺𝗆𝖾𝑝:𝐴⊑𝖢𝑎′:𝐴′
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, 𝑆⊑𝗍𝗒𝑈 comes from the term premise and 𝑇⊑𝗍𝗒𝑈 is explicit, so 𝑈 is a common less-precise upper bound of the left cast’s endpoints. In CPr-CastR, the term premise gives 𝑆⊑𝗍𝗒𝑈 and the last premise gives 𝑆⊑𝗍𝗒𝑉, so 𝑆 is a common more-precise lower bound of the right cast’s endpoints.
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 𝑇⊑𝗍𝗒𝑈; rule CPr-CastR types the right cast and has 𝑆⊑𝗍𝗒𝑉. Rule CPr-Blame uses T-Blame on the left and its right typing premise. ◻
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. ◻
Suppose Γ⊑𝖼𝗍𝗑Γ′, 𝑒⊑𝗌𝗋𝖼𝑒′, Γ⊢𝑒⇝𝑎:𝐴,Γ′⊢𝑒′⇝𝑎′:𝐴′. Let ΔΓ,Γ′ contain 𝑥:𝐵⊑𝗍𝗒𝐵′ exactly when the corresponding declarations 𝑥:𝐵 and 𝑥:𝐵′ occur in Γ and Γ′. Then ΔΓ,Γ′⊢𝑎:𝐴⊑𝖢𝑎′:𝐴′.
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.
For Δ=(𝑥1:𝐴1⊑𝗍𝗒𝐴′1,…,𝑥𝑛:𝐴𝑛⊑𝗍𝗒𝐴′𝑛), write 𝜌⊑𝖾𝑛𝑣𝜌′ when 𝜌=(𝑣1/𝑥1,…,𝑣𝑛/𝑥𝑛),𝜌′=(𝑣′1/𝑥1,…,𝑣′𝑛/𝑥𝑛), and every 𝑣𝑖,𝑣′𝑖 is closed and satisfies ⋅⊢𝑣𝑖:𝐴𝑖⊑𝖢𝑣′𝑖:𝐴′𝑖. Simultaneous substitution is written 𝑎[𝜌].
If Δ⊢𝑎:𝐴⊑𝖢𝑎′:𝐴′ and 𝜌⊑𝖾𝑛𝑣𝜌′, then ⋅⊢𝑎[𝜌]:𝐴⊑𝖢𝑎′[𝜌′]:𝐴′. More generally, if one precision-context suffix is left open, substitution for the preceding entries preserves the judgment under that suffix.
Proof. Use rule induction, strengthened to retain an arbitrary open suffix. Rule CPr-Var selects the corresponding pair from 𝜌⊑𝖾𝑛𝑣𝜌′, and constants are unchanged. In CPr-Lam, alpha-rename the binder away from both substitutions and apply the suffix induction hypothesis to the bodies. Application follows from the two operand hypotheses. Cast endpoints, labels, and type-precision premises contain no term variables, so the aligned and one-sided cast rules apply to the substituted operand derivations. For CPr-Blame, ordinary target substitution preserves the right typing premise, after which CPr-Blame applies. ◻
Write Δ⊢𝐹:𝑆⇒𝑇⊑𝖥𝐹′:𝑆′⇒𝑇′ for the judgment generated by the following rules. It assigns input types 𝑆,𝑆′ and output types 𝑇,𝑇′ and includes 𝑆⊑𝗍𝗒𝑆′ and 𝑇⊑𝗍𝗒𝑇′. The plugging property is proved afterward in lemma 23.37. The aligned frame rules are
Δ⊢𝑎:𝐴⊑𝖢𝑎′:𝐴′𝐵⊑𝗍𝗒𝐵′
Δ⊢[]𝑎:(𝐴→𝐵)⇒𝐵⊑𝖥[]𝑎′:(𝐴′→𝐵′)⇒𝐵′
FPr-AppL
Δ⊢𝑣:𝐴→𝐵⊑𝖢𝑣′:𝐴′→𝐵′
Δ⊢𝑣[]:𝐴⇒𝐵⊑𝖥𝑣′[]:𝐴′⇒𝐵′
FPr-AppR
𝑆⊑𝗍𝗒𝑆′𝑆∼𝖼𝑇𝑆′∼𝖼𝑇′𝑇⊑𝗍𝗒𝑇′
Δ⊢⟨𝑇⇐𝑆⟩𝑝[]:𝑆⇒𝑇⊑𝖥⟨𝑇′⇐𝑆′⟩𝑝[]:𝑆′⇒𝑇′
FPr-Cast
Lift frame precision to evaluation contexts with the following two composition rules:
𝑆⊑𝗍𝗒𝑆′
Δ⊢[]:𝑆⇒𝑆⊑𝖥[]:𝑆′⇒𝑆′
FPr-Hole
Δ⊢𝐸:𝑆⇒𝑇⊑𝖥𝐸′:𝑆′⇒𝑇′Δ⊢𝐹:𝑇⇒𝑈⊑𝖥𝐹′:𝑇′⇒𝑈′
Δ⊢𝐹[𝐸]:𝑆⇒𝑈⊑𝖥𝐹′[𝐸′]:𝑆′⇒𝑈′
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.
If Δ⊢𝐸:𝑆⇒𝑇⊑𝖥𝐸′:𝑆′⇒𝑇′ and Δ⊢𝑎:𝑆⊑𝖢𝑎′:𝑆′, then Δ⊢𝐸[𝑎]:𝑇⊑𝖢𝐸′[𝑎′]:𝑇′. If the next left reduction lies in the hole of two related frames, a simulation of the hole reduction lifts through those frames.
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. ◻
Proof of Lemma 23.38 — Common precision fixes a cast's shape
Proof. For clause 1, if 𝑆 is a ground base, inversion of 𝑅⊑𝗍𝗒𝑆 gives 𝑅=𝑆; inversion of 𝑅⊑𝗍𝗒𝑇 then gives 𝑇=𝑆 because 𝑇 is ground. If 𝑆=?→?, inversion gives 𝑅=𝑅1→𝑅2; 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. ◻
Proof of Lemma 23.39 — One-sided injection and ground-tag coherence
Proof. Induct on the finite precision derivation, using the outer forms of both closed values. A constant, lambda, application, or blame rule cannot conclude an injection on either specified side. For an aligned cast, an outer injection forces target type ?, ground source type, and a value operand. The aligned mate also has target ?. Its value grammar forces a ground injection; regularity of the operand premise relates the two ground source types, so lemma 23.38(1) makes their tags equal.
For CPr-CastL, a left injection has target ?; its premise therefore relates its ground payload to a closed right value at ?. Clause 4 of lemma 23.12 makes that right value an injection. Clause 2 of the induction hypothesis gives precision between the payload type and its tag, hence equality of the two ground tags by lemma 23.38(1). If instead the right injection was already present in the premise, clause 2 of the induction hypothesis gives either an equal-tag left injection or source-type precision to its tag. A retained left cast over the former would be active and hence not a value. In the latter case the retained value is either an injection, when the source ground tag is equal to 𝐺′, or an arrow wrapper, when its target arrow is more precise than the necessarily arrow-shaped ground tag 𝐺′. These are exactly the two alternatives in clause 2.
For CPr-CastR, a right injection newly created by the rule has ground source 𝐺′; regularity of its operand premise gives 𝐴⊑𝗍𝗒𝐺′. If the right injection was already present in that premise, the induction hypothesis applies after stripping the retained cast. A retained right cast above an injection would have unknown source and could be neither a wrapper nor an injection, contradicting the assumed value form. These cases establish both clauses. Clause 1 applied when both values are injections gives the final assertion. ◻
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. ◻
Grounding and expansion replace one cast descriptor by two. For a cast descriptor put 𝜒(𝑇,𝑆):=⎧{
{
{⎨{
{
{⎩3𝑇=?and𝑆isneitherunknownnorground,3𝑆=?and𝑇isneitherunknownnorground,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 weightcw(𝑎) 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(⟨𝑇⇐𝑆⟩𝑝𝑎)=𝜒(𝑇,𝑆)+cw(𝑎). Let sz(𝑎) be ordinary syntax-tree size, counting every constructor once, and put st(𝑎):=(cw(𝑎),sz(𝑎))∈ℕ×ℕ, 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 𝑧=⟨?⇐ℕ⟩𝑞0,𝑚=⟨𝟐⇐?⟩𝑝𝑧,𝑎=⟨?⇐𝟐⟩𝑟𝑚. Two uses of CPr-CastL derive 𝑎⊑𝖢𝑧. The three descriptors have total cast weight three. The inner mismatch produces ⟨?⇐𝟐⟩𝑟𝖻𝗅𝖺𝗆𝖾𝑝; the outer pending cast is the only remaining descriptor, so the first component drops to one. Its propagation to 𝖻𝗅𝖺𝗆𝖾𝑝 lowers that component to zero. Hence the lexicographic measure strictly descends at both stuttering steps.
Write 𝑎⟼+𝑏 when 𝑎 reaches 𝑏 by one or more target steps; thus ⟼+ is the transitive closure of ⟼, whereas ⟼∗ also permits zero steps.
Let 𝐷 relate closed targets 𝑎 and 𝑎′. If 𝑎⟼𝑏 contracts E-Beta or E-WrapApp, then there are 𝑏′ and a precision derivation relating 𝑏 to 𝑏′ such that 𝑎′⟼+𝑏′.
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 (𝜆𝑥.𝑎)𝑣, invert that CPr-App instance. Apply lemma 23.40 to its two premises: 𝑎′1⟼∗𝑣′1,𝑎′2⟼∗𝑣′2. By lemma 23.12(3), 𝑣′1 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. ◻
Proof of Lemma 23.43 — One-step simulation with accounted stuttering
Proof.Frame lifting. Induct on 𝐷, 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 (⟨𝐴2→𝐵2⇐𝐴1→𝐵1⟩𝑝𝑣)𝑤⟼⟨𝐵2⇐𝐵1⟩𝑝(𝑣(⟨𝐴1⇐𝐴2⟩¯𝑝𝑤)). 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 ⟨𝑇⇐𝑆⟩𝑝𝑣, lemma 23.40 derives a right reduction to an operand value related to 𝑣. 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
1↦0
projection or mismatch
decrease of at least 2 to 0
grounding or expansion
3↦1+1
E-Blame
discarded frame and contents
Identity gives the representative stutter calculation cw(⟨𝑆⇐𝑆⟩𝑝𝑣)=1+cw(𝑣) and cw(𝑣), hence 1↦0. Grounding and expansion give 3↦1+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 𝐹[𝖻𝗅𝖺𝗆𝖾𝑝] by 𝖻𝗅𝖺𝗆𝖾𝑝 either discards a cast or, when no cast occurs in 𝐹, strictly decreases syntax size. Thus every constructed zero-step branch satisfies st(𝑏)<st(𝑎). ◻
Proof. Write the finite left path as 𝑎=𝑎0⟼⋯⟼𝑎𝑘=𝑏. Starting with the given precision derivation, lemma 23.43 constructs, for every 𝑖<𝑘, a right path 𝑎′𝑖⟼∗𝑎′𝑖+1 and a derivation relating 𝑎𝑖+1 to 𝑎′𝑖+1. Concatenation yields 𝑎′=𝑎′0⟼∗𝑎′𝑘, with 𝑏=𝑎𝑘 related to 𝑏′=𝑎′𝑘. ◻
Unknown values recover typed self-application, so the infinite clause cannot be derived from normalization.
Put 𝛿:=𝜆𝑥:?.(⟨?→?⇐?⟩𝑝𝑥)𝑥,𝑑:=⟨?⇐?→?⟩𝑞𝛿. Then 𝛿:?→?, 𝑑:?, and 𝑑 is a ground injection value. The closed typed term 𝛿𝑑:? has the exact cycle 𝛿𝑑⟼(⟨?→?⇐?⟩𝑝𝑑)𝑑⟼𝛿𝑑. Thus type safety holds, but strong normalization does not.
This cast representation also exposes a space cost. Let 𝛿0=𝛿, and for 𝑘<4 put 𝛿𝑘+1=⟨?→?⇐?→?⟩𝑝𝑘𝛿𝑘,𝑑4=⟨?⇐?→?⟩𝑞𝛿4. Every 𝛿𝑘 is a wrapper value; an arrow identity cast does not contract merely because its payload is a value. Evaluating 𝛿4𝑑4 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 𝑛 cycles the continuation contains 4𝑛 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.
Proof. Fix the infinite left sequence and its initial precision derivation 𝐷0. Apply lemma 23.43 successively. For each stage 𝑖, choose the resulting derivation 𝐷𝑖+1 for the next left residual. The same instance gives either a nonempty finite right segment or no right step and st(𝑎𝑖+1)<st(𝑎𝑖). 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(𝑎𝑁)>st(𝑎𝑁+1)>st(𝑎𝑁+2)>⋯ in the lexicographic order on ℕ×ℕ, which is well founded. Hence the concatenation contains infinitely many right steps and is an infinite reduction from 𝑎′. ◻
Proof. Starting from 𝑎=𝑎0, define 𝑎𝑖+1 to be the unique term with 𝑎𝑖⟼𝑎𝑖+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 𝑎 agree step by step by determinism, while values and blame have no successor, so the alternatives are disjoint. ◻
Let 𝑒⊑𝗌𝗋𝖼𝑒′ be closed, let ⋅⊢𝑒:𝐴, and let ⋅⊢𝑒⇝𝑎:𝐴,⋅⊢𝑒′⇝𝑎′:𝐴′ be their unique elaborations; existence follows from theorem 23.29 and uniqueness from proposition 23.9. Then:
if 𝑎⟼∗𝑣, then 𝑎′⟼∗𝑣′ for some value 𝑣′ with ⋅⊢𝑣:𝐴⊑𝖢𝑣′:𝐴′;
if 𝑎′⟼∗𝑣′, then either 𝑎⟼∗𝑣 with ⋅⊢𝑣:𝐴⊑𝖢𝑣′:𝐴′, or 𝑎⟼∗𝖻𝗅𝖺𝗆𝖾𝑝 for some 𝑝.
Proof of Theorem 23.48 — Dynamic gradual guarantee
Proof. By lemma 23.33, ⋅⊢𝑎:𝐴⊑𝖢𝑎′:𝐴′. For clause 1, finite simulation relates a right residual to 𝑣, and value catch-up reduces that residual to the required related value.
For clause 2, apply operational trichotomy to 𝑎. If 𝑎 reaches blame, the second alternative holds. If it reaches a value 𝑣, clause 1 gives a value 𝑤′ with 𝑎′⟼∗𝑤′ and ⋅⊢𝑣:𝐴⊑𝖢𝑤′:𝐴′. Since also 𝑎′⟼∗𝑣′, determinism and terminality give 𝑤′=𝑣′. The remaining trichotomy case is impossible: infinite simulation would give 𝑎′⟼𝜔, whereas the assumed finite path to the terminal value 𝑣′ and determinism exclude any infinite path from 𝑎′. ◻
Let 𝑒=((𝜆𝑦:𝟐.𝑦)((𝜆𝑥:?.𝑥)0)ℓ𝑖)ℓ𝑜,𝑒′=((𝜆𝑦:?.𝑦)((𝜆𝑥:?.𝑥)0)ℓ𝑖)ℓ𝑜, with source positions ℓ𝑖 and ℓ𝑜 on both sides. We have 𝑒⊑𝗌𝗋𝖼𝑒′ and both terms typecheck. The inner call injects a natural at ?. The precise outer binder projects it to 𝟐 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.
The equations define a base guard and a function-contract action in the target. For ground 𝐺,𝐻, put 𝗀𝗎𝖺𝗋𝖽𝑞𝐺(⟨?⇐𝐻⟩𝑝𝑣)=𝑣if𝐺=𝐻,𝗀𝗎𝖺𝗋𝖽𝑞𝐺(⟨?⇐𝐻⟩𝑝𝑣)=𝖻𝗅𝖺𝗆𝖾𝑞if𝐺≠𝐻. For a function value 𝑣:𝐴1→𝐵1, define its contract action on a value 𝑤:𝐴2 by 𝖿𝗎𝗇𝖼𝗈𝗇𝑝𝐴1,𝐵1;𝐴2,𝐵2(𝑣,𝑤):=⟨𝐵2⇐𝐵1⟩𝑝(𝑣(⟨𝐴1⇐𝐴2⟩¯𝑝𝑤)). These are meta-level abbreviations for the target terms on the right-hand sides; their reductions are those of definition 23.6.
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 ¯𝑝 and its result label is 𝑝, 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 𝗂𝗌𝗐𝗋𝖺𝗉. On a function-wrapper value it returns 𝗍𝗋𝗎𝖾; on every other target value it returns 𝖿𝖺𝗅𝗌𝖾. This operation is absent from definition 23.5. It is not part of the chapter’s value, blame, and divergence observation.
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 ⟨𝐴→𝐵⇐𝐴→𝐵⟩𝑝𝑣 to 𝑣. Both terms are values in the base target, but the extended observation distinguishes them: 𝗂𝗌𝗐𝗋𝖺𝗉(𝑣)=𝖿𝖺𝗅𝗌𝖾,𝗂𝗌𝗐𝗋𝖺𝗉(⟨𝐴→𝐵⇐𝐴→𝐵⟩𝑝𝑣)=𝗍𝗋𝗎𝖾, provided 𝑣 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 𝑣 is not. The two defining clauses of 𝗂𝗌𝗐𝗋𝖺𝗉 give the displayed Boolean results. A language exposing wrapper identity or allocation therefore needs a new observation theorem. ◻
The descriptor ⟨𝐵⇐𝐴⟩𝑝 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 𝐴 and returns at type 𝐵; 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 𝗂𝗌𝗐𝗋𝖺𝗉 calculation shows why wrapper identity cannot be added to that observation without changing the theorem.
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 |𝑏|=𝑏,|𝑛|=𝑛,|𝑥|=𝑥,|𝜆𝑥:𝐴.𝑎|=𝜆𝑥:𝐴.|𝑎|,|𝑎1𝑎2|=|𝑎1||𝑎2|,|⟨𝐵⇐𝐴⟩𝑝𝑎|=|𝑎|. 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 |𝑎|=|𝑎′| or one ordinary call-by-value step |𝑎|⟼|𝑎′| 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.
In the SD calculus of Osera, Sjöberg, and Zdancewic, write 𝑠:𝑆 for a term of the simply typed sublanguage and 𝑡:𝑇 for a term of the dependently typed sublanguage. The compatibility judgment 𝑆⟺𝑇 permits the explicit boundaries 𝖲𝖣𝑇𝑆(𝑡):𝑆,𝖣𝖲𝑆𝑇(𝑠):𝑇. Both sublanguages use call-by-value contexts, and those contexts descend into their boundary operands.
Suppose a constructor has the paired declarations 𝐶:𝑆1→𝐴,𝐶:(𝑦:𝑇1)→𝐵𝑡1, and constructor-indexed marshalling functions satisfy 𝖺𝗋𝗀𝖳𝗈𝖲𝐶(𝑣)=𝑢 and 𝖺𝗋𝗀𝖳𝗈𝖣𝐶(𝑢)=𝑣. The constructor roots are 𝖲𝖣𝐵𝑡𝐴(𝐶𝑣)⟶𝖲𝖣𝐶𝑢, and 𝖣𝖲𝐴𝐵𝑡(𝐶𝑢)⟶𝖲𝖣(𝑡̃=[𝑣/𝑦]𝑡1)▹𝐶𝑣. Here ⟶𝖲𝖣 denotes the comparison calculus’s one-step reduction, and 𝑡̃=𝑡′▹𝑞 is the dependent guard: it reduces to 𝑞 when the closed first-order indices are equal and to 𝖾𝗋𝗋𝗈𝗋 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.
Assume the paired constructor signature and compatibility rules of SD, and assume its user-supplied 𝖺𝗋𝗀𝖳𝗈𝖲, 𝖺𝗋𝗀𝖳𝗈𝖣, 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 𝖾𝗋𝗋𝗈𝗋, 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.
Consistency is a symmetric, non-transitive permission to insert a cast; it is neither equality nor subtyping.
Type safety classifies blame as a result. Blame safety then excludes one face under an explicit positive or negative hypothesis.
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.
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].
★★☆ Let 𝑖=𝜆𝑛:ℕ.𝑛 and 𝑤=⟨?→?⇐ℕ→ℕ⟩𝑝𝑖. Derive ⋅⊢𝑖:ℕ→ℕ⊑𝖢𝑤:?→? and apply the two functions to the related arguments 0:ℕ and 𝑧=⟨?⇐ℕ⟩𝑟0:?. Reduce 𝑤𝑧 completely. Exhibit the domain cast ⟨ℕ⇐?⟩¯𝑝 and result cast ⟨?⇐ℕ⟩𝑝, and derive the target precision between the two final values.
★★☆ Reprove the E-WrapApp case of preservation as a complete typing tree. The domain premise must use ¯𝑝 and the result premise must use 𝑝. Then show exactly which premise is lost if the domain cast is incorrectly oriented as ⟨𝐴2⇐𝐴1⟩¯𝑝.
★★☆ 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 𝑒⊑𝗌𝗋𝖼𝑒′. Finally, identify the exact place where theorem 23.46, rather than a normalization theorem, excludes divergence in the proof of clause 2.
★★☆ 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.
★★☆ Replace ¯𝑝 by 𝑝 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.
★★☆ For ((𝜆𝑓:ℕ→ℕ.(𝑓0)ℓ𝑖)(𝜆𝑛:ℕ.𝑛))ℓ𝑜, write its elaboration, including arrow identity wrappers. Interleave target reduction with erasure and verify the simulation claimed in proposition 23.52.
★★★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.