Lectures onType Theory
ch:row-polymorphism: ch:row-polymorphism
appendix sectionsolutions

ch:row-polymorphism: ch:row-polymorphism

Exercise 4.1.

Abbreviate the three field types by H=String, N=Nat, and B=Bool. The original row already has host first: {host:H,port:N,secure:Bξ}. One adjacent exchange puts port first: {host:H,port:N,secure:Bξ}=r{port:N,host:H,secure:Bξ}. To put secure first, move it left twice: {host:H,port:N,secure:Bξ}=r{host:H,secure:B,port:Nξ}=r{secure:B,host:H,port:Nξ}. Every exchange is between distinct labels. The assumptions that ξ lacks all three labels rebuild the Row-Ext premises on both sides of each exchange, so these raw equalities are equalities under P.

If the last label is another port, formation already fails. After forming the inner row {port:Nξ}, adding the outer port would require P{port:Nξ}\port. Predicate simplification reaches at the displayed matching head. Consequently there is no strict row on which the proposed exchange calculation could begin.

Exercise 4.2.

Let e:α,r:Rec({:βξ}),P={ξ\}. The update abbreviation expands as {:=er}={=er}. Rule Q-Restrict removes the exposed field and preserves the single tail: r:Rec(ξ). Rule Q-Extend then combines e:α with that residual record: {=er}:Rec({:αξ}). Its only new premise is Pξ\. The same predicate is also exactly the premise that forms the input row {:βξ} and the output row {:αξ}.

Abstracting the two term variables and generalizing gives αβξ.(ξ\)αRec({:βξ})Rec({:αξ}). The same ξ at all three stages expresses preservation of every untouched field.

Exercise 4.3.

Put ρ={error:Stringξ}. The assumptions on ξ imply Pρ\ok, because errorok. The required term is caseok e of {ok=xok=fx,yembedoky. The scrutinee has type Var({ok:αρ}). In the matching branch, x:α, hence fx:β. Choose residual row ρ in Q-Inject; its lacks premise gives ok=fx:Var({ok:βρ}). In the residual branch, y:Var(ρ). Rule Q-Embed chooses the fresh alternative type to be β and gives embedoky:Var({ok:βρ}). Both branches therefore have the same result type. Expanding ρ gives exactly Var({ok:β,error:Stringξ}). Only the ok payload passes through f; every other tag enters the residual branch and is embedded unchanged.

Exercise 4.4.

Suppose the lookup has been typed, after any final row conversion, as q{m=wR}.:τ. Inverting Q-Select gives a closed strict row ρ with q{m=wR}:Rec(ρ),ρ{:τρ}. Peeling the record value at its run-time head gives a type υ and a row ρ0 such that ρ{m:υρ0},qw:υ,qR:Rec(ρ0). Thus ρ0ρm.

Because m, deleting m does not remove the field. Deletion commutation gives (ρm)(ρ)m. Equivalently, the finite-map characterization exposes ρ0{:τ(ρ)m}. Convert the typing of R along this equality and apply Q-Select: qR.:τ. This is exactly the type of the source redex, so the primitive reduction {m=wR}.R. preserves τ.

exercise 4.5.

Write the displayed rows with their tails exposed. Their formation needs P0={ξ\x,ξ\y,ζ\x,ζ\z}. Insertion first exposes the x field on the right, passing the distinct z field. It unifies α with Nat and leaves {y:Boolξ}{z:Stringζ}. To expose y on the right, insertion reaches the open tail ζ. Choose fresh η and put ζ={y:Boolη}, generating the strictness requirement η\y. Deleting the exposed field leaves {z:Stringη}, so the final tail equation sets ξ={z:Stringη}. Thus one printed MGU is U=[Nat/α,{z:Stringη}/ξ,{y:Boolη}/ζ], and normalization of P0[U] gives Q={η\x,η\y,η\z}. For the two additional predicates, ξ[U]\z={z:Stringη}\z,ζ[U]\y. Hence the equation problem has the displayed MGU, but the problem augmented by either requested predicate has no admissible solution.

exercise 4.6.

Give x fresh type α. Injection chooses fresh ξ and returns {ξ\ok},Var({ok:αξ}). For embedding, choose a fresh type β for the new error alternative and a fresh row variable ζ for the old row. Solving Var({ok:αξ})Var(ζ) sets ζ={ok:αξ}. Its new requirement ζ\error normalizes to ξ\error. Lambda formation and generalization give αβξ.(ξ\ok,ξ\error)αVar({error:β,ok:αξ}). If ξ contained ok, injection would duplicate its selected alternative; if it contained error, embedding would duplicate the newly added alternative. The two residual predicates are therefore independent and both necessary.

exercise 4.7.

In the first row, only host is smaller than ok, so the offset is 1. In the second, only error is smaller than host, so that offset is also 1. These counts are unchanged if the two fields are displayed in the opposite syntactic order.

An ok injection into the one-alternative row has tag 0. Embedding it past a new host alternative inserts a smaller label, so evidence increments the tag to 1. Embedding the result past error inserts another smaller label and increments it to 2. The payload never changes; only its position in the canonical ordered sum does.

Exercise 4.8.

Let A={host:String,port:Nat,secure:Bool},B={port:Nat}. Since the label set of B is contained in that of A, Width derives A<:B. From r:A, Sub gives r:B. With portOf:BNat, application yields portOfr:Nat.

For the row-polymorphic derivation, instantiate portOf:αξ.(ξ\port)Rec({port:αξ})α by α=Nat,ξ={host:String,secure:Bool}. The chosen row lacks port by two unequal-head reductions followed by L-Empty. Row permutation identifies the instantiated domain with Rec(A), so the application again has type Nat.

Width subtyping may discard the host and secure fields when it views A as B. A fixed result type for adding timeout can mention the fields retained in B, but contains no variable linked to all the fields forgotten by subsumption. It therefore cannot prove that those arbitrary fields survive. A row type can express that link: ξ.(ξ\timeout)Rec(ξ)Rec({timeout:τξ}). The same ξ in domain and codomain preserves all three input fields in this instance.

Exercise 4.10.

The principal declaration is withSecure:ξ.(ξ\secure)Rec(ξ)Rec({secure:Boolξ}). Set ξ[G]={host:String}. Its predicate discharges by {host:String}\secureϵρ\secure. Ground discharge therefore gives qwithSecure:Rec({host:String})Rec({secure:Bool,host:String}). The argument is typed by Q-Empty, Q-Const, and Q-Extend: q{host="db"{}}:Rec({host:String}). Application is therefore ground typed at the displayed result record type. Its reduction is (λr.{secure=truer}){host="db"{}}{secure=true{host="db"{}}}.

Now try a ground row containing secure:Nat, for example ξ[G]={host:String,secure:Nat}. Normalization of the required predicate first passes the unequal host head and then reaches the matching one: ξ[G]\secure{secure:Nat}\secure. Hence G is not admissible and there is no corresponding ground instantiation or application typing.

exercise 4.11.

Because (V,QV) is a solution of the original problem, principality of (U,QU) supplies an admissible R:QUQV such that VQVU;R on every problem variable. Interchanging the two solutions supplies an admissible S:QVQU with UQUV;S. Literal equality is neither needed nor generally true: row permutation and the names chosen for fresh tails are invisible to the equation problem.

For example, solve {x:Natξ}{y:Boolζ} under the two input strictness predicates. One run gives ξ[U]={y:Boolω},ζ[U]={x:Natω},QU={ω\x,ω\y}. Applying U to the two original rows displays the same finite map in different orders: {x:Nat,y:Boolω}QU{y:Bool,x:Natω}. The adjacent exchange is legal because xy; the two predicates in QU form both displays strictly. A run that chooses ν instead gives the analogous (V,QV); it may also print the two substitutions and predicates in the opposite order. Define ω[R]=ν and ν[S]=ω, acting identically elsewhere. Both are admissible, and the two displayed factorization equations hold modulo their target predicate contexts. Thus the fresh names and print orders differ while the principal solution does not.

Exercise 4.9.

Give the new values types α and β, the old and m fields types γ and δ, and let the remaining row be ξ. Take P={ξ\,ξ\m},m. The principal scheme is αβγδξ.PαβRec({:γ,m:δξ})Rec({:α,m:βξ}).

For the inner update, first permute the input to expose m: Rec({:γ,m:δξ})Rec({m:δ,:γξ}). Restriction and extension calculate {m:=br}={m=brm},rm:Rec({:γξ}),{m=brm}:Rec({m:β,:γξ}). The extension premise reduces to P{:γξ}\m, which follows from ξ\m and m.

For the outer update, permute the intermediate row to expose : {m:β,:γξ}{:γ,m:βξ}. Then ({m:=br}):Rec({m:βξ}),{=a({m:=br})}:Rec({:α,m:βξ}). This extension uses the other predicate, ξ\.

Reversing the updates produces the displayed result row {m:β,:αξ}, which is equal to the former result by one adjacent exchange. Operationally each restriction removes one distinct field and each extension restores it with its new value. Since m, the two operations affect disjoint map entries; the final finite maps are identical even if the record spines display the fields in different orders.

Exercise 4.12.

In the scoped-duplicate record fragment, row and term formation admit repeated labels, record values retain repeated fields, and the operational rules for selection and restriction stop at the first displayed matching field. Equality may exchange adjacent distinct labels, but it never exchanges two equal labels. Consequently the relative order of the occurrences of any one label is invariant.

The sentence in the original canonical-forms proof that fails is the claim that the satisfied lacks predicate makes the new label absent and hence that extension creates exactly the finite map of the extended row. There is no lacks premise in the new calculus, and a value may contain several entries with the same label, so it is not a finite map from labels to values.

The repaired record canonical-form statement is this: if a closed value has a scoped-duplicate record row, then for each label it contains exactly the same ordered sequence of occurrences as the row. The payloads at those occurrences have the corresponding field types. Selection returns the payload at the first occurrence, and restriction removes that occurrence while preserving the order and types of all later occurrences. The proof is induction on the displayed record spine. Extension adds one leading occurrence; an exchange of distinct labels changes no same-label sequence.

This statement does not by itself justify an update notation containing an unqualified ρ. When ρ contains several occurrences, deleting the first, the last, or another occurrence gives different residual rows, often with different next selected types; equal labels cannot be exchanged to identify those results. The update equation becomes sound only after restriction is specified to remove the first displayed occurrence. Then extension installs the replacement as the new first occurrence and leaves all later occurrences available. No claim about variants follows: their run-time tags would additionally need to record the nesting level of equal labels.

Exercise 4.13.

Every ground instance of ξ.(ξ\)Nat has result type Nat. Its evidence argument nevertheless depends on the chosen row. If ξ=ϵρ, the canonical offset is 0. If ξ={m:A} with m<, the canonical offset is 1. Thus the target scheme abstraction is applied to certificates describing different layouts, even though neither layout variable occurs in the visible result type.

For an open judgment the usual condition is ftv(P)ftv(Γ,τ); for a closed scheme it is ftv(P)ftv(τ). The displayed scheme violates the second condition because ξ occurs in P but not in Nat.

Run W on the case term. Injection first returns P0={ξ0\},S0=id,τ0=Var({:Natξ0}), with fresh ξ0. The case clause chooses fresh α,ξ1 and solves Var({:Natξ0})Var({:αξ1}). Its principal solution follows the solver’s fixed left-variable orientation: α=Nat and ξ0=ξ1, retaining Q0={ξ1\}. The matching branch has x:Nat and returns zero:Nat. The residual branch has y:Var(ξ1) and returns the same type. The final branch equation is the reflexive equation NatNat, so W returns ({ξ1\},U0,Nat). After closed generalization, its scheme is precisely of the ambiguous form above.

One repair is to reject a principal inference result when definition 7.53 fails; this is the policy selected by the main evidence-passing compiler. A different language could default every hidden row to a documented ground row, for example ϵρ, and consequently pass offset 0. That choice is deterministic but discards the ambiguous row polymorphism, so it must be part of the language specification rather than an unstated elaboration step.

Practical route.

The row solver of exercise 7.12 is built in appendix F; its success replay and rejected boundary cases are frozen in appendix E.

Search the book

Type to search the local edition.