• No results found

Encoding Unrestricted Recursion

In document MLS module Sealing and Functor Design (Page 171-174)

(ω;C;ref(v))7→(ω[x7→v];C;x) (40) x∈dom(ω) ω(x) =v (ω;C;get(x))7→(ω;C;v) (41) x∈dom(ω) (ω;C;set(x, v))7→(ω[x:=v];C;hi) (42) x6∈dom(ω) (ω;C;callccC(x. e))7→(ω[x7→ C];C;e) (43) x∈dom(ω) ω(x) =Cx (ω;C;throwC(x, v))7→(ω;Cx;v) (44)

Figure 7.10: Dynamic Semantics Extensions for References and Continuations

is C and that is what callcc will bind to x before evaluating e. Then what is the “type” of C? Although explicit continuations are not part of our language, we can nonetheless think of C as a function with argument type C that, when applied, may dereference any of the recursive variables associated with the names inS. Thus, the most appropriate arrow-like type forCwould be C −→S D for some return type D. Under supportS, though, this arrow type is equivalent to C −→∅ D, or in other wordscont(C).

Figure 7.10 gives the extensions to the dynamic semantics for mutable state and continuations. We extend storesωto contain mappings from locationsxto continuationsC. The rules for mutable state are completely straightforward. The rules for continuations are also fairly straightforward, since the machine state already makes the current continuation explicit. Proving type safety for these extensions requires only a simple, orthogonal extension of the proof framework from Sec- tion 7.2.6. The definition of run-time contexts is extended to include variables of type ref(C) and

cont(C), and the definition of store well-formedness is extended as follows:

Definition 7.3.1 (Run-Time Contexts)

A context Γ is run-time if the only bindings in Γ have the form X, x : boxT(C), x : ref(C) or x:cont(C).

Definition 7.3.2 (Machine Store Well-formedness)

A store ω iswell-formed, denoted Γ`ω [S], if Γ`ω [S] according to Definition 7.2.8 and also: 1. ∀x:ref(C)∈Γ. ∃v. ω(x) =v and Γ`v: C [S]

2. ∀x:cont(C)∈Γ. ∃C. ω(x) =Cand Γ` C : Ccont[S]

7.4

Encoding Unrestricted Recursion

Despite all the efforts of the type system, there will always be recursive termssaferec(X . x: C. e) for which we cannot statically determine thatecan be evaluated without dereferencingx. For such cases it is important for the programmer to have the fallback approach of using the unrestricted recursive term constructrec(x: C. e) defined in Chapter 6, with the understanding that dereferences of xwill be saddled with a corresponding run-time cost in order to guarantee type safety.

The point of this section is to illustrate that the unrestricted rec(x: C. e) may be encoded in terms ofsaferec(X. x: C. e) if we extend the language with primitives formemoized computations. The syntax and static semantics of this extension are given in Figure 7.11. First, we introduce a

Types C ::= · · · |compS(C)

Terms e::= · · · |delayS(e)|force(v)

S ∪ S1=T =S ∪ S2 Γ`C1≡C2 [T] Γ`compS1(C1)≡compS2(C2) [S] (45) Γ`e: C [S ∪ T] Γ`delay(e) :compT(C) [S] (46) Γ`v:compT(C) [S] T ⊆ S Γ`force(v) : C [S] (47)

Figure 7.11: Static Semantics Extensions for Memoized Computations

type compS(C) of locations storing memoized computations. A value of this type is essentially a thunk of type unit−→S C whose result is memoized after the first application.

The primitive delayS(e) creates a memoized location x in the store bound to the suspended terme. Whenx is forced (byforce(x)), the expressionestored at xis evaluated to a value v, and then x is backpatched withv. During the evaluation of e, the location x is bound to junk; if x is forced again during this stage, the machine raises an error. Thus, every force of x must check to see whether it is bound to an expression or junk. Despite the difference in operational behavior, the typing rules for memoized computations appear just as if compS(C), delayS(e) and force(v) were shorthand for unit −→S C, λSx:unit. e and vhi, respectively. I use comp(C) and delay(e) as shorthand forcomp(C) anddelay(e), respectively.

We can now encode an unrestricted form of recursion. This construct has a typing rule similar to the one given in Section 6.2:

Γ, x: comp(C)`e: C [S] Γ`rec(x: C. e) : C [S]

The recursive variable x is dereferenced by writing force(x) (similar to fetch(x)). The encoding is as follows:

rec(y: C. e) def= force(saferec(X. x:comp(C).delayX(let y=unbox(x)in e)))

It is easiest to understand this encoding by stepping through it. First, a newrecursive location x is created, bound to junk. Then, the delay creates a new memoized location z bound to the expression let y = unbox(x) in e. Next, the saferec backpatches x with the value z and returns z. Finally, z is forced, resulting in the evaluation of let y =unbox(x) in e, which steps to e[z/y]. Assuming this evaluates to a value v, the location z will then be backpatched with v. If z is dereferenced during the evaluation ofe[z/y] (by an invocation offorce(y) in the originale), then a run-time error will be reported.

Essentially, one can view the saferec in this encoding as tying the recursive knot on the mem- oized computation, while the memoization resulting from the force is what actually performs the backpatching. Observe that if we were to givecomp(C) a non-memoizing semantics,i.e.,to consider it synonymous with unit→C, the above encoding would have precisely the fixed-point semantics

7.4. ENCODING UNRESTRICTED RECURSION 157

Machine States Ω ::= · · · |Error

Continuation Frames F ::= · · · |fillxwith •

x6∈dom(ω)

(ω;C;delayS(e))7→(ω[x7→e];C;x) (48) x∈dom(ω) ω(x) =e

(ω;C;force(x))7→(ω[x:=?];C ◦fillx with •;e) (49) x∈dom(ω) (ω;C ◦fillxwith •;v)7→(ω[x:=v];C;v) (50) x∈dom(ω) ω(x) =? (ω;C ◦force(•);x)7→Error (51) Γ`x:comp(C) [S] Γ`fillx with •: C⇒C [S] (52)

Figure 7.12: Dynamic Semantics Extensions for Memoized Computations

of recursion. Memoization ensures that the effects in eonly happen once, at the first force of the recursive computation.

The dynamic semantics for this extension is given in Figure 7.12. To evaluate delayS(e), we create a new memoized location in the store and bind e to it (Rule 48). To evaluate force(x), we proceed to evaluate the term e to which x is bound, pushing on the continuation stack a memoization frame (fillx with •) to remind us that the result of evaluating eshould be memoized at x(Rules 49 and 50). Ifx is instead bound to junk, then we must be in the middle of evaluating another force(x), so we step to an Error state which halts the program (Rule 51).

Extending the type safety proof to handle memoized computations is straightforward. Con- tinuation frame well-formedness is extended with Rule 52 for memoization frames. We must also update the definition of run-time contexts to include memoized location bindings, well-formed and terminal states to includeError, and store well-formedness to account for memoized locations:

Definition 7.4.1 (Run-Time Contexts)

A context Γ is run-time if the only bindings in Γ take the form X, x : boxT(C), x : ref(C), x:cont(C) orx:compT(C).

Definition 7.4.2 (Machine State Well-formedness)

A machine state Ω iswell-formedif either Ω =Erroror Ω is well-formed according to Definition 7.2.9.

Definition 7.4.3 (Terminal States)

A machine state Ω isterminal if either Ω =Error or Ω is terminal according to Definition 7.2.13.

Definition 7.4.4 (Store Well-formedness)

A store ω iswell-formed, denoted Γ`ω [S], if Γ`ω [S] according to Definition 7.3.2 and also:

In document MLS module Sealing and Functor Design (Page 171-174)