Lectures onType Theory
ch:higher-order-effects: ch:higher-order-effects
appendix sectionsolutions

ch:higher-order-effects: ch:higher-order-effects

exercise 24.1.

If the raw source computations are stored in the operation tag, the catch component must be polymorphic in the result type of the whole folded tree. Its shape is acatch:SyntaxH(A)×SyntaxH(A)(AG(B))G(B). The continuation has already been folded, but the two arguments still have type SyntaxH(A), not G(A). To use them, the component would need an additional polymorphic argument τX:SyntaxH(X)G(X)for every X. This is a translator for the complete source signature H. It cannot be supplied by the catch component alone, because either source computation may contain any operation of H. Adding a new summand to H therefore changes the argument expected by the catch clause, which is exactly the lost modularity.

exercise 24.2.

Let 3={0,1,2}. Define one operation first(t) for each t:Ty, and put ForkFirst(first(t))=(3,λi.Val(t)),RetFirstH(first(t))=Val(t). For Mi:HeftyH(Val(t)), the smart constructor is first(M0,M1,M2)=impure(first(t),ψ,pure), where ψ(0)=M0,ψ(1)=M1,ψ(2)=M2. Every fork component has the result index demanded by its position, and the operation continuation receives the selected value.

exercise 24.3.

With the correct bind, the branches of C remain pure and the continuation is g. The selected branch returns , the continuation emits one tick, and readTicks therefore returns 1: C=HgHreadTickstickreadTicks1. For the structural bind (24.3), the selected branch is first changed from pure() to tick, and the node continuation is also g. After the branch tick returns, the continuation emits the second tick: Cbind×gHreadTicksticktickreadTicks2. Both programs remain well typed. The count exposes the misplaced recursive call.

exercise 24.4.

Expanding the definition through (24.6) gives fmapH(f,pure(x))=pure(f(x)),fmapH(f,impure(o,ψ,k))=impure(o,ψ,fmapH(f,)k). The identity law is the right-unit equation of theorem 24.8: fmapH(id,M)=M=Hpure=M. For composition, associativity and the left unit give fmapH(g,fmapH(f,M))=(M=H(puref))=H(pureg)=M=H(λx.pure(f(x))=H(pureg))=M=H(puregf)=fmapH(gf,M).

exercise 24.5.

Work in the fixed-type catch fragment specified in the exercise. Choose xy:A and a constant g:AHeftyH(B), say g(a)=pure(b0). Put Cx=catch(pure(x),pure(x)),Cy=catch(pure(y),pure(y)). A structural catamorphism with generator g presents the same data to its operation algebra in both cases. The operation tag is the same, each folded fork component is g(x)=pure(b0)=g(y), and the folded continuation is g in both trees. Hence a fixed component βg must return the same result for Cx and Cy.

Correct bind does not return the same result. Equation (24.3) gives Cx=Hg=impure(catch(t),λ_.pure(x),g),Cy=Hg=impure(catch(t),λ_.pure(y),g). Their forks differ because xy. Thus no algebra which sees only the recursively folded fields can define hefty bind for every g.

exercise 24.6.

The source smart constructor has an empty fork. Its operation continuation returns the source response after the transport supplied by the higher-order insertion witness; that transport is definitionally trivial for the front summand. Applying (24.5), then (24.5.1), gives elaborateElift,w(o)=impure(ιw(o),pureqw,o). A target response has type r:RetΔ(ιw(o)). The transport qw,o(r):RetΔ0(o) converts it to the response expected by the source operation continuation, and pure returns that response in the target tree.

exercise 24.7.

Correct hefty bind leaves both catch branches unchanged and replaces the catch continuation by f. After the catamorphism, the clause data are ψ^(tt)=throw,ψ^(ff)=elaborateE(M),k^=elaborateEf. Put N=elaborateE(M),K=elaborateEf. Substitution into (24.18) yields mask(runThrow(throw))=maybe(K,N=K)=pure(none)=maybe(K,N=K)=N=K. The translated continuation occurs once, as the continuation sequenced after the fallback result. It was not pushed into the fallback before the catch clause was interpreted.

exercise 24.8.

The canonical reassociation sends operation tags by inl(inl(o1))inl(o1),inl(inr(o2))inr(inl(o2)),inr(o3)inr(inr(o3)). Its inverse is determined by reversing these three equations. The fork and return families on corresponding tags are definitionally the same component families, so transport changes only the nested sum tag.

For o1, both composite algebras dispatch to E1; for o2, both dispatch to E2; for o3, both dispatch to E3. In every case the fork and continuation arguments are passed through unchanged. Hence the algebras agree after transport along reassociation.

Without transport, the left algebra has domain (H1H2)H3, while the right algebra has domain H1(H2H3). Literal equality would compare terms of different types.

exercise 24.9.

The alternative component retains only the protected branch and sequences its result into the operation continuation. For (24.8), that continuation is the translation of get. Thus elaborateEprotected(transact)=put(1)((put(2)throw)=(λ_.get))=put(1)put(2)throw. The last equality uses the empty response type of throw: no continuation branch can reach get. The state handler, started at zero, performs 012 and then forwards the throw. The outer throw handler maps that node to none. The final state is not returned by the chosen state handler, but the update to 2 occurs before the abort.

exercise 24.10.

For S:Freeε(Maybe(A)), define qS=maybe(puresome,S). Then (24.29) says that observable catch is C(R,S)=R=qS. Hence C(C(R1,R2),R3)=(R1=qR2)=qR3=R1=(λz.qR2(z)=qR3). If z=some(x), the function in the last line returns pure(some(x)). If z=none, it returns R2=qR3=C(R2,R3). Therefore it is pointwise qC(R2,R3), and C(C(R1,R2),R3)=C(R1,C(R2,R3)). Substitute Ri=run(Mi) and apply (24.29) in both directions. The proof concerns observed results; the two nested source trees have different constructor shapes.

exercise 24.11.

  1. This is exactly theorem 24.13.

  2. This is a clause of theorem 24.30, not a consequence of typing alone.

  3. Neither theorem supplies it. One needs a source operational semantics, a target step relation modulo administrative equations, and a simulation relation.

  4. Neither theorem supplies it. One needs a closed-program effect invariant and a progress or handler-completeness theorem for the target.

  5. Neither theorem supplies it. One needs a surface elaboration or typechecking algorithm and a soundness theorem relating its output to HeftyH.

exercise 24.12.

Write mi=ψ^(i) and let h=runThroww. Put ri=maskw(h(mi)). A left-to-right component is Efirst(first(t),ψ^,k^)=r0=maybe(k^,r1=maybe(k^,r2=maybe(k^,throw))). Each mi has type FreeΔ(Val(t)). Therefore h(mi):FreeΔ(Maybe(Val(t))) and maskw(h(mi)):FreeΔ(Maybe(Val(t))). The continuation has type k^:Val(t)FreeΔ(A), while the innermost target throw has type FreeΔ(A). Hence every maybe is a function from Maybe(Val(t)) to FreeΔ(A), and every displayed bind returns FreeΔ(A).

On the given input, handling the first branch produces pure(none), so evaluation continues to the second. The second branch produces pure(some(2)). Hence its maybe invokes k^(2). For the smart constructor, k^=pure, so the result is pure(2). The third branch is not inserted into the target tree.

exercise 24.13.

Let h=runThroww and let maskw be the masking map of equation 24.2. Let throw denote the target throw tree. The required induction predicate is P(m):h(maskw(h(m))=maybe(pure,throw))=h(m). This is the target equality obtained by expanding run(catch(M,throwH)).

If m=pure(x), then h(m)=pure(some(x)). The masked tree binds through the some branch to pure(x). The outer handler returns the displayed value, which is h(m).

If m=throw, then h(m)=pure(none). The maybe function selects target throw, and the outer handler returns pure(none)=h(m).

If m=impure(o,k) for a residual operation o, then the inner handler, masking, bind, and outer handler all forward o. The left side of (B.1) is impure(o,λr.h(maskw(h(k(r)))=maybe(pure,throw))), while the right side is impure(o,hk). They agree pointwise by the induction hypothesis.

This direct proof fuses three traversals into one induction predicate. The factorized chapter proof isolates handler–bind distribution. It also isolates handler–mask interaction, then obtains catch–throw2 from the ordinary right-unit law. The direct proof is shorter for this law; the factorized proof also supplies observable catch and congruence.

exercise 24.14.

The catch calculation is independent of the final-state observation. By (24.8), both runs receive the same target tree Ttr, where Ttr:=put(1)put(2)get. The discard-state equations of (24.4) give runState0(Ttr)=runState1(put(2)get)=runState2(get)=runState2(pure(2))=pure(2). The outer throw handler therefore returns pure(some(2)), and empty-signature elimination yields some(2).

The pair-returning handler takes the same two state transitions and the same get response. Only its pure equation differs: runState0×(Ttr)=runState1×(put(2)get)=runState2×(get)=runState2×(pure(2))=pure(2,2). After runThrow and empty-signature elimination, the result is some(2,2). Both handlers observe the committed update 012; the second merely exposes the final store. Thus the comparison changes the result interface, not the catch elaborator or its rollback policy.

exercise 24.15.

Represent a parenthesized sum by a binary tree whose leaves are the ordered signatures [H1,,Hn]. Define flatten by concatenating the leaf lists. An operation tag in the tree determines a unique leaf index i and a component operation o:OpHiH: descend left or right according to its sum injections and count the leaves skipped on right descents. Conversely, a leaf index and component operation rebuild a unique tag by following the tree. These maps are inverse by induction on the binary tree.

Transport the fork and return families by the same recursion. At a leaf they are the component families. At an internal node, a left or right tag reduces to the corresponding child family, so transport introduces no new data beyond the equality identifying the selected leaf.

Now associate to each ordered list of elaborators [E1,,En] the flattened dispatcher which sends leaf index i to Ei. Induction on either parenthesization shows that its iterated -composition transports to this dispatcher. Hence any two parenthesizations agree after both are transported to the common flattened signature.

A permutation changes the leaf index assigned to an operation. Proving invariance under it requires an explicit permutation of operation tags and a corresponding reordering of component elaborators. Reassociation preserves the ordered list and therefore cannot supply that theorem.

exercise 24.16.

Abbreviate E(M):=elaborateEtr(M),Ts(M):=runStates(E(M)),FN:=maybe(pure,E(N)). Write s,MRTT=admTs(M), where =adm is generated by the free-monad units and associativity together with the displayed handler and mask equations. The five source roots have the following nonempty target calculations. For return sequencing, preservation of source bind by the catamorphism and target left unit give the following result. The embedding clauses used here are E(get(x.M))=get=(λx.E(M)),E(M;N)=E(M)E(N). Thus get consumes its returned value by bind, whereas source sequencing discards its left result. The return calculation is Ts(return(n);M)=embedding/elaborationrunStates(pure(n)=(λ_.E(M)))=unitTs(M).

For put, Ts(put(n);M)=elaborationrunStates(put(n)E(M))=putTn(M). For get, Ts(get(x.M))=elaborationrunStates(get(x.E(M)))=getrunStates(E(M)[s/x])=substitutionTs(M[s/x]). Thus the last tree is related to s,M[s/x].

For a successful catch, the pure equation of the throw handler is runThrow(pure(n))=pure(some(n)). Masking and target bind then select the success continuation: Ts(catch(return(n),N))=catchrunStates(pure(some(n))=FN)=justTs(return(n)). For a thrown protected branch, Ts(catch(throw,N))=catchrunStates(mask(runThrow(throw))=FN)=throw,maskrunStates(pure(none)=FN)=nothingTs(N). For completeness, the congruence obligation is the following compatibility lemma, proved by induction on the one-step derivation (sequencing and catch are the two context cases): if s,PHs,P, then substituting E(P) by its nonempty admissible calculation to E(P) inside either the sequencing continuation or the displayed catch context—including mask(runThrow([]))=FN—gives the related tree at state s. Compatibility of target bind, runThrow, mask, and runState with their displayed equations proves each induction step; the state parameter changes only in the put case already calculated above. This is the needed lemma, rather than an unstated appeal to ordinary term congruence.

This proves the requested root simulation for the finite grammar. It does not supply surface type inference, a theorem for arbitrary higher-order sums, or absence of unhandled operations in an arbitrary target tree.

Search the book

Type to search the local edition.