Modal Types For Mobile Code
Modal Types For Mobile Code
CMU-CS-08-126
May 13, 2008
1 Introduction 1
1.1 Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Organization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2 Located programming 5
2.1 ConCert and Grid/ML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.2 Marshaling and location . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
v
4 Modal typed compilation 61
4.1 The at modality . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
4.2 MinML5 external language . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
4.2.1 Addresses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
4.2.2 Syntax and static semantics . . . . . . . . . . . . . . . . . . . . . . . 63
4.2.3 Dynamic semantics . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
4.3 MinML5 internal language . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
4.3.1 Dynamic semantics . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
4.4 Elaboration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
4.4.1 Elaboration in Twelf . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
4.4.2 The elaboration relations . . . . . . . . . . . . . . . . . . . . . . . . . 78
4.5 Continuation passing style . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
4.5.1 Dynamic semantics . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
4.5.2 Type safety . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
4.6 CPS conversion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
4.6.1 Static correctness . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
4.6.2 CPS conversion in Twelf . . . . . . . . . . . . . . . . . . . . . . . . . 92
4.7 Closure conversion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
4.7.1 Closure conversion in Twelf . . . . . . . . . . . . . . . . . . . . . . . 103
4.8 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
vi
5.4.9 Code generation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
5.5 Runtime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
5.5.1 Server 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
5.5.2 Communication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
5.5.3 Client runtime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
5.5.4 Marshaling and unmarshaling . . . . . . . . . . . . . . . . . . . . . 197
5.6 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
6 Applications 205
6.1 Watchkey . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
6.2 Chat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
6.3 Wiki . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
6.4 Spreadsheet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
6.5 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
7 Conclusion 217
7.1 Related work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217
7.1.1 Modal logic in distributed computing . . . . . . . . . . . . . . . . . 219
7.1.2 Distributed ML-like languages . . . . . . . . . . . . . . . . . . . . . 221
7.1.3 Languages for web applications . . . . . . . . . . . . . . . . . . . . 222
7.2 Future work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
7.2.1 Modal type systems . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
7.2.2 ML5 and its implementation . . . . . . . . . . . . . . . . . . . . . . 225
7.2.3 Web programming . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
7.2.4 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230
vii
A.8.6 CPS conversion and static correctness . . . . . . . . . . . . . . . . . 326
A.8.7 Closure conversion and static correctness . . . . . . . . . . . . . . . 332
Bibliography 365
viii
List of Figures
ix
4.12 Elaboration of values, expressions, and valid values . . . . . . . . . . . . . 73
4.13 Type families defining the EL and IL . . . . . . . . . . . . . . . . . . . . . . 79
4.14 Syntax of the CPS language . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
4.15 CPS: mobility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
4.16 CPS language: value typing . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
4.17 CPS language: expression typing . . . . . . . . . . . . . . . . . . . . . . . . 86
4.18 Value instantiation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
4.19 CPS evaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
4.20 CPS translation: types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
4.21 CPS translation: convert . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
4.22 CPS translation: values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
4.23 Type families defining the CPS language . . . . . . . . . . . . . . . . . . . . 93
4.24 The CC language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
x
Chapter 1
Introduction
This thesis project’s goal is to demonstrate that modal type systems provide an elegant
and practical means for controlling local resources in spatially distributed computer
programs. To do this, I have designed a new formulation of modal logic from which I
derived a modally-typed lambda calculus. I then extended this calculus to a full-fledged
programming language. I implemented the programming language by building a type-
directed compiler and a runtime system specialized to web applications. I then demon-
strated my language’s effectiveness by building realistic web applications with it. In
this dissertation I present the theoretical components of my work and describe the im-
plementation components; the source code and on-line demos are available separately.1
1.1 Overview
Some history will be useful to provide context for the problem I solve. This work arose
from the ConCert project, in which we built theory and infrastructure for trustless peer-
to-peer “Grid Computing.” Grid computing, which is named by analogy with electric
power grids, is the practice of coupling diverse computational resources into a large
shared computer. Grid applications are mobile in the sense that they run on multiple
different hosts, in different locations, during the course of their execution. One of the
programming languages we implemented for the ConCert project was Grid/ML, an
ML-like language with simple support for massively parallel computations.
Grid/ML. In Grid/ML, the programmer writes his whole Grid application as a unified
program. This program is made of two parts: the client, which runs only on the user’s
computer and interacts with him, and the mobile code, which runs on arbitrary hosts
in the network. (The ConCert infrastructure automatically allocates the mobile code to
idle hosts.)
Grid/ML is practical for a certain class of problems, and has been used for a few
Grid programming experiments. However, it has two major shortcomings. First, the
1
They can be found at [Link]
1
mobile code is agnostic to where it is running, so applications must treat the hosts in
the network uniformly; it is not possible to make use of special resources only available
at certain sites. Since distributed computing is often motivated by the desire to make
use of resources other than mere processing power, this rules out an important set of
applications.
Second, even with the assumption of uniformity for the mobile code, Grid/ML still
has a distinction between client code and mobile code. The client is the part of the
Grid/ML application that the user interacts with, and the mobile code is what is actually
run on the Grid. Certain operations, such as I/O, can only be performed on the client
and result in run-time failure if used in mobile code.
My solution to both problems is to enrich the ML type system with a concept of
place. By doing so, the language is able to support location-aware programming, so that
network locations need not be treated uniformly. As a consequence, the client is simply
another place, and we will be able to distinguish its capabilities from the capabilities of
other hosts. Because we use a type system, we can make these distinctions statically, ex-
cluding errors before the program is ever run. Although the shortcomings of Grid/ML
inspired ML5 (and the language would be an appropriate successor to Grid/ML), ML5
is an independent language suitable for many sorts of distributed programs. The cur-
rent implementation is not designed for Grid computing with ConCert; rather, we target
the Web as our application domain. This is a particular case of distributed programming
where there are exactly two hosts, with widely different capabilities: the web browser
and the web server.
Modal logic and a modal type system. Type systems for functional languages like
ML have a close connection to logic, called the Curry-Howard isomorphism. Under
this view, the propositions of intuitionistic logic are interpreted as the types of a pro-
gramming language. Proofs of propositions then become programs inhabiting those
types. Different logics, viewed through the lens of the Curry-Howard isomorphism,
give rise to a variety of elegant and useful type systems for programming languages.
In order to develop a type system with a notion of place, we use a logic with the
ability to reason spatially, namely modal logic. The propositional logic upon which ML
is based is concerned with the truth of propositions from a single universal viewpoint.
Modal logic introduces the concept of truth from multiple different perspectives, which
are called “worlds.” The logic is then able to reason simultaneously about truth in these
worlds. Under the Curry-Howard view, these worlds become hosts in the network, and
so our type system is correspondingly endowed with a notion of place.
2
used in a safe way: Localized resources can only be used in the correct place. When
code or data do not depend on their location, we are also able to indicate this in the
type system. This allows them to be safely used anywhere. This brings us to the thesis
statement:
Although modal types naturally address spatial distribution, this is only one facet
of distributed computing. In particular, a logical approach to concurrency and failure is
beyond the scope of this project. However, ML5 has rudimentary support for these for
the purpose of building realistic applications.
Web programming. The Web has evolved from a way of linking formatted documents
together to a platform for application delivery. The modern web application is pre-
sented as a single web page with a client side program (written in JavaScript) that com-
municates with a server side program (written in Java or another server programming
language) to access databases and other server resources. This style is known as “AJAX”
and is an instance of distributed programming; the two hosts (browser and server) run
in different locations and have different capabilities. For example, only the server can
access the database, and only the client can interact with the user.
3
1.2 Organization
The dissertation is organized following the same trajectory that the research took: an
end-to-end study of programming language design and implementation from the iden-
tification of the problem domain to the crafting of applications using the completed
implementation. I begin by briefly describing the ConCert project for the purpose of
showing how its programming language, Grid/ML, has a type system too weak for safe
distributed programming (Section 2.1). I then present our formulation of modal logic,
called Lambda 5, which is the basis of a new calculus and type system for distributed
programming (Section 3.2). To promote this calculus to a full-fledged programming
language requires extending it in a number of ways. We first explore a classical variant,
called C5, that adds continuations to the language (Section 3.4); these are used in com-
pilation and in the implementation of threads and some programming idioms. We also
find that we must extend the calculus with support for global reasoning (Section 3.5)
in order to write some programs; this will also be required for compilation. I study
the first few phases of compilation for our extended calculus, formalizing and proving
properties in the Twelf system (Chapter 4). This concludes the foundational portion of
the project.
Given these foundations, I then describe the programming language ML5 (a straight-
forward integration of the calculus into ML) and its implementation (Chapter 5). Fol-
lowing this I present my example applications, built with ML5 (Chapter 6). I conclude
with a discussion of related work and ideas for the future (Chapter 7). The formalization
of the calculi and proofs appear in full in the appendix.
4
Chapter 2
Located programming
5
any sort of distributed resource.
Programmers can produce cords by hand or by using one of the programming lan-
guages we designed for ConCert, such as Grid/ML [85]. Even though cords have this
uniform view, a Grid/ML program is not entirely uniform; it also includes a client part
that can run only on the user’s machine (because it accesses local resources like the
keyboard and screen). Therefore, we still have an issue with controlling access to local
resources. Let’s look at an example program in Grid/ML that illustrates the language
and some of the problems with it:
let
val f = openfile "[Link]"
val f2 = openfile "[Link]"
val cords =
map (fn n => submit (fn () => factor n)) inputs
6
submit : (unit → α) → α cord
waitall : α cord list → α list
A value of type α cord is a running (or finished) computation that returns a result of
type α. Grid/ML allows α to be instantiated with any type. In this case each factoring
task is an int list cord. After submitting the cords, the program then waits for
them all to complete, and writes the results to the output file.
The file I/O that the program performs is an effect. Such effects are allowed only in
the part of the Grid/ML program that runs on the client. This is because I/O interacts
with the external world, and so it depends on the place in which the code is executed.
Therefore, an effect inside cord code would violate the requirement that it be agnostic
about the host on which it runs. In Grid/ML, such errors are detected only at run-time.
For example, if we modify the program so that the body of factor tries to write to the
file f2 or read from the file f, then the program will abort at that operation. Although
both files are in scope, the file descriptors do not make sense when executing at remote
sites—even if we wanted to allow I/O in cord code, we would not be able to access those
files once execution has left the client.
This does not necessarily mean that open file descriptors can never leave the client.
Suppose the program is modified to be higher-order:
let
(* ... *)
fun factor n =
let
(* ... *)
val factors = trial (floor (sqrt (real n)))
in
(fn () => writeresult factors)
end
(* ... *)
in
app (fn g => g ()) results
end
Now, instead of returning the list of factors directly, each cord returns a function that
writes the result to the file on the client. The client consumes these results by calling the
functions. This program has the same behavior as the first version. The reference to
the local file safely makes a round trip from the client to the cord code and back in the
environment of each function. We wish to permit programs like this one: Although this
example is gratuitous, we will see many examples of useful higher-order programming
in our applications (Chapter 6). Additionally, the process of compilation via CPS and
closure conversion (Sections 4.5, 4.7) introduces higher-order functions and round-trip
dependencies that were not evident in the source program.
7
2.2 Marshaling and location
To account for programs like this one, ML5 is designed around a concept of place. Each
expression and variable in the program has associated with it a location. For an ex-
pression, the location indicates the place in which the expression may be evaluated to
produce a value. For a bound variable, the location indicates where the value bound
to it can be consumed. An important facet of this style is that it allows one part of the
program—which runs on host A—to safely manipulate code and data that can only be
executed or used at a different host B.
To further illustrate why this is important, let us compare a mainstay of distributed
programming: the remote procedure call. Most RPC systems limit the forms of data that
can be passed as arguments and returned as results from a remote procedure call. For
example, CORBA limits the types of RPC arguments to a few simple base types such
as strings and arrays [138, 145]. In Java RMI [37], objects matching the Serializable
interface can be passed to remote methods, but this does not include references to local
resources such as file handles. The Serializable interface is an empty “marker” interface
and so can be applied to any class; it merely acts as a record of the programmer’s intent
that instances should be mobile. Therefore, almost no static checking is performed, and
errors are caught at runtime or produce unintended behavior [72].
Java both fails to statically exclude unsafe programs (those that attempt to transmit
local resources and use them remotely) and unnecessarily terminates programs that
would be safe (those that transmit local resources, but do not misuse them). This is
because it makes the decision at the moment the transmission is about to occur, but
this is too early to know if the resource will be used in the wrong place, and too late
to reject the program statically. This is a very common design in mobile programming
languages, as discussed in Section 7.1.
I contend that this is a design mistake, and that it stems from a conflation of two
separate notions. First is the implementation technique of marshaling, which is sim-
ply a way of representing a piece of data in a format suitable for transmission on a
network. The second is the potential mobility of data; the semantic quality of mak-
ing sense at more than one place. In most languages these ideas are conflated because
distributed applications essentially consist of a collection of separate programs, each
of which can only understand data from its own perspective. When a Java program
performs a remote procedure call, the function that is called can’t help but require that
all its arguments make sense to it, because the only notion of “making sense” is one of
“making sense locally.” Therefore, every remote procedure call requires that the argu-
ments shift from making sense to the caller to making sense at the receiver. Because this
shift in perspective always occurs at the same time as marshaling, they seem like the
same operation.
Located programming allows us to clearly separate these concepts by allowing us
to reason from multiple simultaneous perspectives. A remote procedure call consists of
two steps: preparing arguments that are appropriate for the callee, and then sending
those arguments to the callee. The caller performs the first step by preparing values
whose location is the callee (it can create them from scratch, use callee values that it
8
received previously, or convert some of its own values, subject to the rules of the type
system). The type system ensures that any such values will indeed make sense to the
callee. The implementation then uses its marshaling facility to transmit the values to the
callee and unmarshal them. We permit any value to be marshaled, so this step can only
fail if the network malfunctions. We are then able to analyze the issue of what values can
be converted from one location to another as a semantic issue, not an implementation
one.
We use logic to address this semantic issue in a principled way, by deriving the type
system of ML5 from a spatial modal logic. Because we prove that the logic has strong
soundness and completeness properties, we have good reason to believe that our type
system properly embodies the relevant notions of locality and mobility—that it is not
accidentally too restrictive. (In some cases we make concessions, but do so conscious
of their nature.) The correspondence between the type theory and proof theory also
serves as a way of recasting ideas in a different light. In this project this lead to concrete,
unanticipated improvements in both the logics and programming language.
ML5 also decouples the remote call construct from the procedure (or method) con-
struct; it simply includes a way to nest an expression to be evaluated at a remote site
within a local expression. The expression may be a function call, in which case it looks
like RPC; it may be some other expression, in which case the “arguments” described
above are whatever free variables are used in that expression.
Local resources are an important part of distributed programming, and the located
programming model gives us an effective way of controlling them. However, much
of a distributed program is code or data that makes sense anywhere. For example, in
the Grid/ML program above, the factor function refers to the functions sqrt and @,
which are defined as part of the standard library, use no local resources, and can thus
be used anywhere. If we required them to have locations, we would need to explicitly
coerce them before using them in other locations, which would make the language very
cumbersome. An important feature of the ML5 type system is that it also accounts for
values like these that can be used anywhere. As a result, when ML5 is used to write non-
distributed applications or when the distributed applications do not use local resources,
its type system degenerates into ML’s.
We have now motivated a type system enhanced with a notion of place. To sum-
marize, the type system for ML5 will ensure that localized resources are only used in
the correct place by associating with each sub-expression the place in which it will be
evaluated, and associating with each bound variable the place where it may be used.
We will also introduce support for bindings that are usable in any place, since this is
very common. The foundation of this type system comes from modal logic, which is
introduced in the next chapter.
9
10
Chapter 3
In this chapter I present the modal logics and calculi that we use as the basis of the
ML5 type system. I begin with an overview of modal logic and motivate our choices for
this research project in Section 3.1. In Section 3.2 I give the simplest formulation of our
modal logic and lambda calculus, and then extend it with continuations in Section 3.4
and validity in Section 3.5.
∆ ` A true
meaning that the proposition A is true under the assumptions in ∆. Modal logic rela-
tivizes truth to worlds by instead using the judgment
Γ ` A true @ w
that is: A is true from the perspective of the world w. The context Γ collects hypotheses
of the form B true @ w0 for various propositions B and worlds.1 This will be the only
1
Throughout this work we mean for Γ to support the natural structural properties such as exchange
and weakening. These properties are made precise through the definition of the languages in Twelf, in
the Appendix.
11
propositions A, B, C ::= 2A | A ⊃ B | 3B | A at w | A ∧ B | A ∨ B | p
primitive propositions p
world expressions w ::= w | ω
world variables ω
world constants w
Figure 3.1: Syntax of worlds and propositions. The propositions are defined by the rules
of the modal logic in question; we use the same syntax in each logic for brevity. Worlds
can either be named constants or variables.
notion of truth until we reintroduce universal reasoning in Section 3.5. We may also
hypothesize the existence of a world (written ω world, where ω is a world variable) or its
accessibility from another world (written w1 w2 ).
Given this judgment, we can now prescribe the logic. We call this style of describing
the logic, which is due to Simpson [126], an “explicit worlds” formulation. Some other
formulations and their computational interpretations are described in Section 7.1.
The connectives ∧, ∨, ⊃ are essentially the same as their non-modal intuitionistic
counterparts. We simply add “ @ w” to each of the judgments, for the same world w,
to allow reasoning as usual within a particular world. (Note however that ∨ E allows
the disjunction eliminated to come from a different world than the conclusion. This
will be of concern in Section 3.2.3.) Similarly, we can only use a hypothesis A @ w to
conclude A at that same world w (Rule hyp). The modal judgment makes it possible
to define new connectives 2, 3 and at. The proposition 2A means that A is true in all
(accessible) worlds. If we know 2A @ w, then we can conclude A @ w0 as long as w can
access w0 (Rule 2 E). The accessibility condition is reflected as a premise of the rule
requiring a proof of w w0 ; the only way to prove this is to use an assumption of that
form (Rule rhyp). To prove 2A, we assume the existence of a hypothetical world about
which nothing is known, assume we can access that world, and then prove A there
(Rule 2 I). If A is true at such a world, then it is true in any accessible world, because it
relies on no assumptions particular to it. Reasoning at a fresh hypothetical world will
be a recurring theme of this work.
The connective 3A means that A is true at some unknown (but accessible) world.
We can prove 3A by giving an accessible world and a proof of A there (Rule 3 I). If we
know 3A @ w0 , then we can reason as follows: Assume the existence of a world ω, assume
that w can access w0 , and assume that A is true there to conclude C @ w (Rule 3 E). The
world variable ω stands in for the actual (now unknown) world in which A is true, just
like when we eliminate an existential type, we do not know the identity of the actual
type. Note that this rule involves three different worlds; this will be of concern in our
computational interpretation of the logic (Section 3.2).
Following Jia and Walker [62], we also find it profitable to include one more connec-
tive not typically seen in modal logic, written A at w. With this proposition the logic
belongs in the family known as “hybrid logics” [59]. Contrary to its name, I will argue
12
Γ ` A@w Γ ` B @w Γ ` A ∧ B @w ∧ E Γ ` A ∧ B @w ∧ E
∧I 1 2
Γ ` A ∧ B @w Γ ` A@w Γ ` B @w
Γ ` A ∨ B @ w0
Γ ` A@w ∨ I Γ ` B @w ∨ I Γ, A @ w0 ` C @ w Γ, B @ w0 ` C @ w
1 2 ∨E
Γ ` A ∨ B @w Γ ` A ∨ B @w Γ ` C @w
Γ, A @ w ` B @ w Γ ` A ⊃ B @w Γ ` A@w
⊃ I ⊃ E
Γ ` A ⊃ B @w Γ ` B @w
Γ, ω world, w ω ` A @ ω Γ ` 2A @ w0 Γ ` w0 w
2I 2E
Γ ` 2A @ w Γ ` A@w
Γ ` A @ w0 Γ ` w w0 Γ ` 3A @ w0 Γ, ω world, w0 ω, A @ ω ` C @ w
3I 3E
Γ ` 3A @ w Γ ` C @w
hyp rhyp
Γ, A @ w ` A @ w Γ, w w0 ` w w0
Figure 3.2: Intuitionistic modal logic (IK), as described by Simpson [126]. We include the
“hybrid” connective at. Different modal logics can be produced by adding deductive
rules for the relation ; here it obeys no additional structural properties. Intuitionistic
S5 results when the relation is reflexive, symmetric, and transitive (Figure 3.3).
13
Γ ` w0 w sym
refl
Γ`w w Γ ` w w0
Γ`w w0 Γ ` w0 w00
trans
Γ ` w w00
in Section 4.1 that this connective is more important and more natural than 2 and 3. It
is simply an internalization of the judgment A @ w as a proposition; to prove A at w we
prove A @ w anywhere (Rule at I). To use a proof of A at w00 , we introduce a hypothesis
A @ w00 and go on to prove another proposition (Rule at E).
In our computational interpretation of modal logic, the worlds will be the hosts in
the network, and proofs at those worlds will correspond to programs that can be exe-
cuted at those worlds. Our next step is to choose an appropriate accessibility relation
between worlds for this application.
3.1.1 Accessibility
The explicit worlds formulation of modal logic allows us to express different logics by
adding deductive rules for the judgment. In order to justify our choice of accessibility
relation, let us build intuition for the intended computational interpretation by looking
at the meaning of some propositions as types:
• A ⊃ B. As in a Curry-Howard view of propositional logic, the proposition A ⊃
B will be the type of functions from A to B.
• 2A. The universal proposition 2A will be the type of code that can run any-
where (accessible) and produce a value of type A, that is, mobile code.
• 3A. The existential proposition 3A will be the type of addresses pointing to an
(accessible) location with a value of type A.
• A at w. The hybrid connective will be the type of an encapsulated value of type
A that we know can be used specifically at w.
Since our computational model is a computer network, we take accessibility to be
the ability to communicate on the network. We then choose an accessibility relation
that is reflexive (a host can “access” itself, via loopback), transitive (a host can forward
messages through the intermediate host) and symmetric (network connections are typ-
ically two-way). This gives us the logic IS5 (Figure 3.3). Some characteristic axioms
of IS5 (given in Hilbert style; in this judgmental presentation they are simply provable
propositions) illustrate why this is an appropriate choice:
• 2A ⊃ A. If we have a piece of mobile code here, we can execute it to produce a
value of type A here.
• A ⊃ 3A. If we have a value of type A, then we can take its address.
• 2A ⊃ 22A. Mobile code is itself mobile.
14
• 33A ⊃ 3A. If we have the address of an address of a value, we can shorten this
to a direct address.
• 32A ⊃ 2A. If we have the address of some mobile code, we can retrieve that
mobile code.
• 3A ⊃ 23A. Addresses are mobile.
• 2(A ⊃ B) ⊃ 3A ⊃ 3B. If we have a mobile function from A to B, and the
address of a value of type A, then we can run that mobile function wherever the A
is, and take the address of the result.
Additionally, some non-theorems illustrate the limitations on mobile code and val-
ues:
Other accessibility relations have been studied in the context of distributed com-
puting. As discussed in Section 7.2, not all real networks (including in particular the
network available for web programming) are actually symmetric or transitive. IS5 is a
reasonable choice because we can usually implement a symmetric and transitive net-
work (an overlay network) atop one that is not (Section 5.5). More importantly for our
purposes, IS5 admits a formulation that dispenses with the accessibility relation, leading
to a more straightforward programming language. This is the topic of the next section.
3.1.2 IS5∪
The reflexive, symmetric, and transitive accessibility relation of IS5 separates worlds
into a set of equivalence classes. Since every world we learn about in a derivation is
related to an existing world, if we start with a single equivalence class then every world
we learn about will also be in that equivalence class, and all worlds will be related.
This suggests a simplification of the accessibility relation to the universal relation,
0 0
where w w for all w and w . Figure 3.4 gives the simplified rules when accessibility is
universal; we call the logic IS5∪ .
IS5∪ and IS5 prove the same exact theorems in the empty context starting with a sin-
gle constant world or set of related worlds; the only difference arises when IS5 reasons
about multiple equivalence classes of worlds simultaneously. (All of the propositions
we used as justification in the previous section are provable in IS5∪ .) For our compu-
tational interpretation, we don’t have any reason to write programs about two uncon-
nected networks, so this distinction is unimportant to us. In fact, these two logics are so
close that many just call the modal logic with universal accessibility S5 [30, 70, 116]. Re-
gardless of its name, this is the modal logic that we base our calculus and programming
language on.
15
Γ ` A@w Γ ` B @w Γ ` A ∧ B @w ∧ E Γ ` A ∧ B @w ∧ E
∧I 1 2
Γ ` A ∧ B @w Γ ` A@w Γ ` B @w
Γ ` A ∨ B @ w0
Γ ` A@w ∨ I Γ ` B @w ∨ I Γ, A @ w0 ` C @ w Γ, B @ w0 ` C @ w
1 2 ∨E
Γ ` A ∨ B @w Γ ` A ∨ B @w Γ ` C @w
Γ, A @ w ` B @ w Γ ` A ⊃ B @w Γ ` A@w
⊃ I ⊃ E
Γ ` A ⊃ B @w Γ ` B @w
Γ, ω world ` A @ ω Γ ` 2A @ w0
2I 2E
Γ ` 2A @ w Γ ` A@w
Γ ` A @ w0 Γ ` 3A @ w0 Γ, ω world, A @ ω ` C @ w
3I 3E
Γ ` 3A @ w Γ ` C @w
hyp
Γ, A @ w ` A @ w
Figure 3.4: IS5∪ . If the relation is the universal relation (w w0 for all w and w0 ), then
we get this simpler deductive system that does not need to track accessibility.
16
3.2 Lambda 5
Having chosen a logic appropriate for our problem domain of distributed computing,
the next step is to add proof terms to it. These proof terms will be a computational
lambda calculus whose types are the propositions of modal logic.
17
Γ ` M : A@w Γ ` N : B @w Γ ` M : A ∧ B @w ∧ E Γ ` M : A ∧ B @w ∧ E
∧I 1 2
Γ ` hM, N i : A ∧ B @ w Γ ` #1 M : A @ w Γ ` #2 M : B @ w
Γ ` M : A@w ∨ I1 Γ ` M : B @w ∨ I2
Γ ` inl M : A ∨ B @ w Γ ` inr M : A ∨ B @ w
Γ ` M : A ∨ B @w
Γ, x:A @ w ` N1 : C @ w Γ, y:B @ w ` N2 : C @ w
∨ I1
case M of
Γ` inl x ⇒ N1 : C @ w
inr y ⇒ N1
Γ, x:A @ w ` M : B @ w Γ ` M : A ⊃ B @w Γ ` N : A@w
⊃ I ⊃ E
Γ ` λx.M : A ⊃ B @ w Γ ` M N : B @w
Γ, ω world ` M : A @ ω Γ ` M : 2A @ w
2I 2E
Γ ` box ω.M : 2A @ w Γ ` unbox M : A @ w
Figure 3.5: Lambda 5 natural deduction, with proof terms. Compared to IS5∪ , we have
added the get rule and constrained the remainder of the rules to act locally.
18
which suspends the execution of the mobile code M until we use unbox to evaluate it.
A value of type 3A can be introduced with here M , if M is of type A at the same world.
When we consume a value of type 3A with letd ω, x = M in N we learn of a new
hypothetical world ω and bind a variable of type A @ ω. Note that all of these rules act
locally. If we want to reason across worlds, we use the get construct. get[w0 ] M at the
world w evaluates the expression M at w0 and returns the resulting value to w. The
evaluation of M at w0 produces a value whose type is A @ w0 , but get[w0 ] M has type
A @ w. We therefore have a restriction on the types A that we can use get on; without
this, all worlds would conclude the same set of facts, making the logic too degenerate
to be useful.
2M 3M
2A mobile 3A mobile
at M
A at w mobile
We restrict get to types that satisfy the mobile judgment, which is given in Figure 3.6.
Thinking computationally, a type is mobile if every value of that type is portable to any
world; for example, strings and integers are mobile types, as are pairs of mobile types.
An encapsulated value A at w is mobile no matter what A is. In Chapter 4 we will see
this property formalized for the proof of type safety.
The mobility judgment also has a logical justification, which we use in the proof that
Lambda 5 is equivalent to IS5∪ below.
Examples. Some examples will help to illustrate the interaction between get and the
local rules. Here are proofs of some of the propositions we used to motivate our choice
of S5 in Section 3.1.1, assuming some constant world w at which to prove them:
1. ` λ[Link] x : 2A ⊃ A @ w
2. ` λ[Link] x : A ⊃ 3A @ w
3. ` λ[Link] ω, y = x in get[ω] y : 33A ⊃ 3A @ w
4. ` λ[Link] ω, y = x in get[ω] y : 32A ⊃ 2A @ w
5. ` λ[Link] ω.get[w] x : 2A ⊃ 22A @ w
6. ` λ[Link] ω.get[w] x : 3A ⊃ 23A @ w
` λf.λ[Link] ω, y = x in get[ω] here ((unbox get[w] f ) a)
7.
: 2(A ⊃ B) ⊃ 3A ⊃ 3B @ w
Examples 1 and 2 are simply the eta expansions of the unbox and here primitives.
Example 3 works by eliminating the outer 3 and moving the inner one from the hypo-
19
Γ =⇒ A @ w Γ =⇒ B @ w Γ, A ∧ B @ w, A @ w, B @ w =⇒ C @ w0
∧R ∧L
Γ =⇒ A ∧ B @ w Γ, A ∧ B @ w =⇒ C @ w0
Γ, A ∨ B @ w, A @ w =⇒ C @ w0
Γ =⇒ A @ w ∨ R Γ =⇒ B @ w ∨ R Γ, A ∨ B @ w, B @ w =⇒ C @ w0
1 2 ∨L
Γ =⇒ A ∨ B @ w Γ =⇒ A ∨ B @ w Γ, A ∨ B @ w =⇒ C @ w0
Γ, A ⊃ B @ w =⇒ A @ w
Γ, A @ w =⇒ B @ w Γ, A ⊃ B @ w, B @ w =⇒ C @ w0
⊃ R ⊃ L
Γ =⇒ A ⊃ B @ w Γ, A ⊃ B @ w =⇒ C @ w0
Γ =⇒ A @ w Γ, A at w @ w0 , A @ w =⇒ C @ w00
at R at L
Γ =⇒ A at w @ w0 Γ, A at w @ w0 =⇒ C @ w00
Γ, ω world =⇒ A @ ω Γ, 2A @ w, A @ w0 =⇒ C @ w00
2R 2L
Γ =⇒ 2A @ w Γ, 2A @ w =⇒ C @ w00
Γ =⇒ A @ w0 Γ, 3A @ w, ω world, A @ ω =⇒ C @ w0
3R 3L
Γ =⇒ 3A @ w Γ, 3A @ w =⇒ C @ w0
init
Γ, A @ w =⇒ A @ w
Figure 3.7: IS5∪ sequent calculus. The sequent calculus is given in terms of left and right
rules instead of introduction and elimination. It has the subformula property and a cut
principle (Theorem 2).
thetical world ω to w using get. Since get works on any mobile type, the same term
has type 32A ⊃ 2A as well (Example 4). Examples 5 and 6 work by constructing box
and moving the argument to that hypothetical world. Example 7 is the most complex.
It takes a mobile function f :2(A ⊃ B) @ w and the address of an argument x:3A @ w.
Deconstructing the address gives us a hypothetical world ω and the argument at that
world y:A @ ω. We travel to that world with get; in order to call the function, we must
get it from our original world w and unbox it. After applying it to a, we take the ad-
dress of the result with here and return that to w. Note that in this example we make
two round trips: one to go to ω, and one to get the mobile function from w. We will be
able to write a more direct program with a single round trip once we introduce validity
in Section 3.5.
20
3.2.3 Soundness and completeness
To prove that Lambda 5 is equivalent to IS5∪ , we will prove that it is sound and complete
relative to a sequent calculus that is itself equivalent to IS5∪ (Figure 3.8). This sequent
calculus appears in Figure 3.7.
The central judgment of the sequent calculus is Γ =⇒ A @ w, where Γ is a series of
hypotheses of the form Bi @ wi . However, the rules are given in terms of left and right
rules instead of introduction and elimination. Every rule (other than init) works by
breaking down a single formula, either on the left or right side, into components. This is
known as the subformula property. Due to this property, it is easy to see what sequent
calculus proofs are possible for a formula. For example, any proof of A ∧ B @ w with no
hypotheses must begin with the rule ∧ R, and thus contain proofs of A @ w and B @ w as
subterms. We can also easily refute the existence of proofs, which give us consistency
easily:
Theorem 1 (Consistency of IS5∪ sequent calculus)
Not all sequents are provable.
Proof: Immediate, by counterexample. Consider the sequent · =⇒ p @ w for some
primitive proposition p and constant world w. The only rule that can conclude p @ w is
init, but the rule does not apply because there are no hypotheses. Therefore, it has no
proof. 2
In the natural deduction, there are many rules that might apply, such as ⊃ E and ∨ E;
the sequent calculus insists that no detours through unrelated propositions are allowed.
The soundness of Lambda 5 relative to IS5∪ sequent calculus will give us consistency of
Lambda 5 as well.
Soundness
In order to prove soundness, we will need a few lemmas. We will use D, E, and F to
stand for derivations, and the syntax
D.
.
D :: J or J
21
of the formula A by a right rule for that connective, and E is a use of the formula A by
a left rule for that connective. Any other case is a commutative case. For example, the
principal case for A at w00 is as follows; suppose
D. 0 E. 0
. .
Γ, Γ0 =⇒ A @ w00 Γ, A at w00 @ w, A @ w00 , Γ0 =⇒ B @ w0
at R at L
D = Γ, Γ0 =⇒ A at w00 @ w E = Γ, A at w00 @ w, Γ0 =⇒ B @ w0
E2 :: Γ, A @ w00 , Γ0 =⇒ B @ w0
and then remove the hypothesis A @ w00 from E2 with cut(D0 , E2 ) to get
F :: Γ, Γ0 =⇒ B @ w0
as required. The first appeal to induction is justified by the smaller input derivation E 0 ,
and the second by the smaller cut formula A. Appendix A.1 contains the entire proof in
machine checkable form as the relation cut. 2
Cut is a standard property of a sequent calculus. The other lemmas are particular to
our modal setting, forming the logical justification for the mobile judgment:
Theorem 3 (Expansion)
If A mobile
then A @ w =⇒ A @ w0
Theorem 4 (Shift)
If A mobile
and Γ =⇒ A @ w
then Γ =⇒ A @ w0
Expansion says that we are licensed to use a hypothesis of mobile type to form the
same conclusion at a different world. Shift says that if we can conclude a mobile type at
one world, we can conclude it at any other world. Note that expansion is an instance of
shift when Γ is A @ w and the first derivation is the init rule, but the proof of shift appeals
to expansion in precisely this case.
The proof of shift is by induction over the derivation of the sequent premise. Sup-
pose the input derivation is of the form
D. 1 D. 2
. .
Γ =⇒ A @ w Γ =⇒ B @ w
∧R
Γ =⇒ A ∧ B @ w
E1 ::Γ =⇒ A @ w0
E2 ::Γ =⇒ B @ w0
22
and therefore
E.1 E.2
. .
Γ =⇒ A @ w0 Γ =⇒ B @ w0
∧R
Γ =⇒ A ∧ B @ w0
The cases introducing the primitively mobile types (2A, 3A, A at w) are non-
inductive. For example, if the derivation is of the form
D.
.
Γ =⇒ A @ w00
at R
Γ =⇒ A at w @ w
D.
.
Γ =⇒ A @ w00
at R
Γ =⇒ A at w @ w0
because at R allows us to reason non-locally. (This is the reason that A at w is mobile for
any A.) The other cases are similar, except for the init case, in which we appeal directly
to expansion. The full proof appears in machine checkable form in Appendix A.1 as the
relation shift. 2
The proof of expansion is a bit more interesting, proceeding by induction on the
derivation of A mobile. We will give all of the cases:
init
2A0 @ w, ω world, A @ ω =⇒ A0 @ ω
2 L
2A0 @ w, ω world =⇒ A0 @ ω
2R
2M 2A0 @ w =⇒ 2A0 @ w0
init
3A0 @ w, ω world, A0 @ ω =⇒ A0 @ ω
3R
3A0 @ w, ω world, A0 @ ω =⇒ 3A0 @ w0
3L
3M 3A0 @ w =⇒ 3A0 @ w0
init
A0 at w00 @ w, A0 @ w00 =⇒ A0 @ w00
at R
A0 at w00 @ w, A0 @ w00 =⇒ A0 at w00 @ w0
at L
at M A0 at w00 @ w =⇒ A0 at w00 @ w0
23
induction hypothesis D1 :: A1 @ w =⇒ A1 @ w0 and D2 :: A2 @ w =⇒ A2 @ w0 . For A1 ∧ A2 ,
the derivation is
weaken.. D1 weaken.. D2
A1 ∧ A2 @ w, A1 @ w, A2 @ w =⇒ A1 @ w0 A1 ∧ A2 @ w, A1 @ w, A2 @ w =⇒ A2 @ w0
∧R
A1 ∧ A2 @ w, A1 @ w, A2 @ w =⇒ A1 ∧ A2 @ w0
∧L
A1 ∧ A2 @ w =⇒ A1 ∧ A2 @ w0
and for A1 ∨ A2 it is
weaken .. D1 weaken.. D2
A1 ∨ A2 @ w, A1 @ w =⇒ A1 @ w0 A1 ∨ A2 @ w, A2 @ w =⇒ A2 @ w0
∨ R1 ∨ R2
A1 ∨ A2 @ w, A1 @ w =⇒ A1 ∨ A2 @ w0 A1 ∨ A2 @ w, A2 @ w =⇒ A1 ∨ A2 @ w0
∨L
A1 ∨ A2 @ w =⇒ A1 ∨ A2 @ w0
which concludes the proof. 2
Note that we have some flexibility in how we define the mobile judgment. The mo-
bility of 2A, 3A and A at w is required for completeness; otherwise our restriction to
local introduction and elimination forms is too severe. However, the rule ∧ M is not
needed anywhere; we can remove it from our natural deduction and retain soundness
and completeness (in fact, normalizing a natural deduction proof by applying Theo-
rems 5 and 6 eliminates uses of ∧ M). We choose to include it because it is natural to
send aggregations of mobile data in distributed programs, and easy to implement. We
could choose to have the rule
A mobile B mobile
⊃ M
A ⊃ B mobile
but doing so requires a more complicated dynamic semantics (the dynamic creation of
mobile proxies for functions). Therefore, we continue to make choices about the formu-
lation of the logic that are influenced by its intended computational interpretation, but
do so in a way that does not affect its underlying meaning.
Given cut and shift, we can state and prove soundness:
Theorem 5 (Soundness of Lambda 5 relative to IS5∪ sequent calculus)
If D :: Γ ` M : A @ w
then E :: p Γq =⇒ A @ w
The operation px1 : A1 @ w1 , . . . xn : An @ wn q erases variables (since they are not used
in the sequent calculus), producing A1 @ w1 , . . . , An @ wn . Soundness simply states that
for every Lambda 5 natural deduction proof (ignoring the proof terms) there exists a
sequent derivation of the same formula, under the same hypotheses.
This proof is by induction over D. The cases where D is an introduction rule are
trivial by induction hypothesis (the right rules in the sequent calculus match the intro-
duction rules in natural deduction). The translation of elimination rules appeals to cut;
for example, if
D. 0
.
Γ ` : 2A @ w
2E
D = Γ ` : A@w
24
then we have by induction hypothesis on D0
E1 :: p Γq =⇒ 2A @ w
and by construction
init
Γ , 2A @ w, A @ w =⇒ A @ w
p q
2L
E2 = Γ , 2A @ w =⇒ A @ w
p q
so by cut(E1 , E2 ) we have
E :: p Γq =⇒ A @ w
as required. The other left rules are similar.
The only other case is get, where we have
F. 1 D. 1
. .
A mobile Γ ` : A @ w0 get
D = Γ ` : A@w
E 0 :: p Γq =⇒ A @ w0
E :: p Γq =⇒ A @ w
as required. The full proof appears in Appendix A.1 as the relation ndseq. 2
Completeness
To show that we have not made Lambda 5 too restrictive, we prove a completeness
theorem.
Theorem 6 (Completeness of Lambda 5 relative to IS5∪ sequent calculus)
If D :: Γ =⇒ A @ w
then E :: Γ0 ` M : A @ w, for some M and Γ0 A Γ
Here Γ0 A Γ if Γ0 has the same hypotheses as Γ, each given a unique variable name.
For this proof we will need the following substitution lemmas:
Theorem 7 (World substitution for Lambda 5)
If Γ ` w world
and Γ, ω world, Γ0 ` M : A @ w0
then Γ, Γ0 ` [ w/ω ]M : [ w/ω ]A @ [ w/ω ]w0
Theorem 8 (Term substitution for Lambda 5)
If Γ ` M : A @ w
and Γ, x:A @ w, Γ0 ` N : C @ w0
then Γ, Γ0 ` [ M/x ]N : A @ w0
25
The world substitution operation [ w/ω ] is defined in the obvious way on proof terms,
types, world expressions, and derivations. The term substitution operation [ M/x ] is sim-
ilarly defined on proof terms. Each substitution proof is by induction on the second
premise. 2
Completeness is proved by induction on D. The right rules proceed directly by in-
duction hypothesis (since the introduction rules take the same form), except that we
may insert uses of get. For example, if
D. 0
.
Γ =⇒ A @ w0
at R
D = Γ =⇒ A at w0 @ w
E 0 :: Γ0 ` M : A @ w0
which is at the wrong world to use with the local-only Lambda 5 rule at I, but by using
get we have
E. 0
.
Γ0 ` M : A @ w0
at M at I
A mobile Γ0 ` M : A at w0 @ w0 get
E = Γ0 ` get[w0 ] M : A at w0 @ w
as required.
The translations of left rules appeal to substitution. For example, if
D. 0
.
Γ, 2A @ w, A @ w0 =⇒ C @ w00
2L
D = Γ, 2A @ w =⇒ C @ w00
2M hyp
2A mobile Γ, x:2A @ w ` x : 2A @ w
get
Γ, x:2A @ w ` get[w] x : 2A @ w0
2E
F = Γ, x:2A @ w ` unbox get[w] x : A @ w0
as required.
26
Disjunction. Disjunction is not so easy. If
D. 1 D. 2
. .
Γ, A ∨ B @ w0 , A @ w0 =⇒ C @ w Γ, A ∨ B @ w0 , B @ w0 =⇒ C @ w
∨L
D = Γ, A ∨ B @ w0 =⇒ C @ w
we have by induction hypothesis
E1 :: Γ0 , x:A ∨ B @ w0 , y:A @ w0 ` M1 : C @ w
E2 :: Γ0 , x:A ∨ B @ w0 , y:B @ w0 ` M2 : C @ w
and would like to apply ∨ E as in
case x of
N = inl y ⇒ M1
inr y ⇒ M2
but cannot because the case object x must be at the same world as the conclusions M1
and M2 in our local-only rule. We cannot use get either; A ∨ B is only mobile when A
and B are mobile. Fortunately, the IS5∪ rule that allows “action at a distance” is in fact
derivable in Lambda 5. The proof we use is
case (get[w0 ] case x of
inl z ⇒ inl (hold z)
inr z ⇒ inr (hold z)) of
inl y 0 ⇒ leta y = y 0
in M1
end
inr y ⇒ leta y = y 0
0
in M2
end
27
cut, expansion, shift substitution
(soundness) (completeness)
Figure 3.8: The soundness and completeness argument for Lambda 5. Lambda 5 is
equivalent to the IS5∪ sequent calculus (Theorems 5 and 6), which is itself equivalent to
IS5∪ (Theorems 9 and 10).
We spent a lot of effort proving that Lambda 5 natural deduction is equivalent to the
more straightforward formulation IS5∪ . The purpose of this was to create a logic whose
proof terms were a lambda calculus with a simple dynamic semantics that can be used
to write distributed applications. In the next section I describe this semantics.
28
values v ::= h v1 , v2 i | inl v | inr v | λ x.M | box ω.M | there[w, v] | held v | x
Γ ` v1 : A @ w Γ ` v2 : B @ w
∧V
Γ ` h v1 , v2 i : A ∧ B @ w
Γ ` v : A@w ∨ V1 Γ ` v : B @w ∨ V2
Γ ` inl v : A ∨ B @ w Γ ` inr v : A ∨ B @ w
Γ, x:A @ w ` M : B @ w Γ ` v : A @ w0
⊃ V at V
Γ ` λ x.M : A ⊃ B @ w Γ ` held v : A at w0 @ w
Γ, ω world ` M : A @ ω Γ ` v : A @ w0
2V 3V
Γ ` box ω.M : 2A @ w Γ ` there[w0 , v] : 3A @ w
hyp Γ ` v : A@w
val
Γ, x:A @ w ` x : A @ w Γ ` val v : A @ w
Figure 3.9: The syntax for Lambda 5 values and their typing rules.
the transfer of control between worlds, as well as give meaning to non-terminating pro-
grams. In our previous paper on Lambda 5 we give a continuation-based semantics
that explicitly stores references to remote objects in tables [89], similar to the way we
implement marshaling (Section 5.5.4). For this discussion our main concern is defining
the location in which computation takes place. The ⇓ relation suffices for that purpose
and is briefer to define than a small-step relation.
The evaluation relation ⇓ is between an expression and a value, and is indexed by
the (concrete) world in which the evaluation takes place:
M ⇓w v
Expressions are the proof terms from Lambda 5, and their typing rules are the infer-
ence rules in Figure 3.5. We provide a different syntactic class for values v, and define
typing for them via a judgment Γ ` v : A @ w for them. These appear in Figure 3.9.
Variables are now considered values, which changes the meaning of the expression
typing rules (the context now contains hypotheses about the typing of values, not ex-
pressions), but we do not repeat the rules here because they would be syntactically
identical. The val rule allows us to use a value as an expression of the same type. The
main thing to notice about the value typing rules is that they are non-local, like the in-
troduction rules from IS5∪ . (For example, at V allows the value within it to be typed at
29
M ⇓w h v1 , v2 i M ⇓w h v1 , v2 i M ⇓w v1 N ⇓w v2
#1 ⇓ #2 ⇓ h,i ⇓
#1 M ⇓w v1 #2 M ⇓w v2 hM, N i ⇓w h v1 , v2 i
M ⇓w v M ⇓w v
inl ⇓ inr ⇓
inl M ⇓w inl v inr M ⇓w inr v
M ⇓w 0 v
val ⇓ get ⇓
val v ⇓w v get[w0 ] M ⇓w v
another world.) This is allowable because, as values, they do not need any more evalua-
tion, and therefore require no “action at a distance.” Because typing is non-local, we will
be able to move certain values from world to world and retain their well-formedness
(Theorem 13). Most of the values resemble the expressions that construct them, but a
value of type 3A is different. It is written there[w, v] where w is a world constant (the
world where A is true) and v is the value well-typed at w. We need both because the
elimination form binds a world variable and value variable.
The definition of evaluation appears in Figure 3.10. Evaluation takes place on closed
terms so there is no case for evaluating variables (they are replaced with values by
substitution). Observe that evaluation only changes worlds in the get ⇓ rule; all other
action happens locally. Additionally, we are always evaluating at a constant world (not
a variable), meaning that there is always a specific concrete world at which to carry out
computation.
30
The main theorem about the evaluation relation is that it preserves well-typedness:
Theorem 12 (Type preservation of ⇓)
If D :: · ` M : A @ w
and E :: M ⇓w v
then F :: · ` v : A @ w
The proof is by induction on the derivation E, and is completely standard except for
the get case. If
M ⇓w 0 v
get ⇓
E = get[w0 ] M ⇓w v
then by inversion on D we have
D1 :: A mobile D2 :: · ` M : A @ w0
E 0 :: · ` v : A @ w0
but require v to be well-typed at the source world w. To prove this we need a lemma
that says that a value of mobile type at one world also has that type at other worlds
(Theorem 13 below). Applying this lemma, we get
F :: · ` v : A @ w
as required. The full proof appears implicitly as the well-typedness of the eval relation
in Appendix A.3. 2
The value shift lemma is stated as follows:
Theorem 13 (Value shift)
If D :: · ` v : A @ w
and E :: A mobile
then F :: · ` v : A @ w0
We consider this the computational justification for the mobile judgment. This proof
is a straightforward structural induction over the derivation E that the type is mobile.
There are a few differences between this and the shift theorem (Theorem 4) we used
in Section 3.2.3. First, we insist that the same value be well-typed at both worlds—in
the shift theorem we allowed the proof to be transformed. This allows the dynamic
semantics to simply send the value from one world to another without modifying it.
Second, we require the value to be closed, so that we do not have to consider the case of
variables. The full proof appears in Appendix A.3. 2
We also desire a progress theorem for ⇓ (otherwise we may have “forgotten” some
rules), but this is difficult to state for a big-step semantics (because of the possibility that
we may make progress but never terminate, and therefore have no finite derivation). In
the Twelf formalization we use its coverage checker [121] to prove that some rule of the
eval relation always immediately applies, even if the proof search process does not
terminate. 2
31
The dynamic semantics for Lambda 5 is close to what we want for our programming
language. When evaluating locally, the semantics match up with the familiar ones from
lambda calculus, giving a clear path to implementation. However, the concepts that it
offers are not enough to write and compile real programs. The two features that we
wish to add are universal reasoning and continuations. Universal reasoning allows us
to avoid explicitly moving values around with get when those values make sense any-
where, so that we are not burdened by the modal type system when we are not using it.
The logical basis for this is discussed in Section 3.5. Continuations allow us to compile
our language via Continuation Passing Style, which is the basis of our implementation
of threads, part of the interface to databases and the GUI, and a useful high-level pro-
gramming construct. The logical basis for continuations is classical modal logic, which
is discussed in the next section.
3.4 C5
Lambda 5 is a computational modal lambda calculus based on intuitionistic S5. In this
section I present C5, a calculus based on classical S5. This gives us a logical explanation
of continuations. Although ML5 supports first-class continuations, ultimately we do
not adopt the specific formalism presented in this section, because the full power of
network-wide continuations suggested by the logic is too heavyweight for our needs.
Therefore, this section can be seen as an excursion, and not necessary for understanding
the remainder of the dissertation.
callcc : (α cont → α) → α
throw : α cont → α → β
32
Γ ` M : A@w Γ ` N : B @w Γ ` M : A ∧ B @w ∧ E Γ ` M : A ∧ B @w ∧ E
∧I 1 2
Γ ` hM, N i : A ∧ B @ w Γ ` #1 M : A @ w Γ ` #2 M : B @ w
Γ, x:A @ w ` M : B @ w Γ ` M : A ⊃ B @w Γ ` N : A@w
⊃ I ⊃ E
Γ ` λx.M : A ⊃ B @ w Γ ` M N : B @w
Γ ` M : A@w Γ ` M : A at w0 @ w Γ, x:A @ w0 ` N : C @ w
at I at E
Γ ` hold M : A at w @ w Γ ` leta x = M in N : C @ w
Γ, ω world ` M : A @ ω Γ ` M : 2A @ w
2I 2E
Γ ` box ω.M : 2A @ w Γ ` unbox M : A @ w
Γ ` M : ⊥@w
⊥E
Γ ` abort M : C @ w
Γ, u:A ? w ` M : A @ w Γ, u:A ? w ` M : A @ w
bc #
Γ ` letcc u in M : A @ w Γ, u:A ? w ` throw M to u : C @ w0
33
deduction system in which there are no elimination rules, only introduction of false-
hood and truth [94]. In each case, the expressiveness of the system comes from the
structural properties that allow for the mixing of proofs and refutations. In C5 natural
deduction we take a deliberately asymmetric view: We push all of the reasoning into the
truth judgment and provide only the structural rules for interfacing with the degener-
ate falsehood judgment. This is because programming with truth is programming with
values—a more familiar style than manipulating continuations. The calculus that re-
sults is just Lambda 5 extended with two structural rules, which are essentially callcc
and throw. We prove that this formulation of the logic sacrifices no expressiveness.
2M 3M
2A mobile 3A mobile
at M
A at w mobile
A mobile B mobile
∧M ⊥M
A ∧ B mobile ⊥ mobile
C5 natural deduction appears in Figure 3.11. The rules for ∧, ⊃, 2, at, and 3 are
identical to those of Lambda 5. We omit ∨, since it can be defined in terms of ⊃, ⊥, and
∧, and anyway would be treated just as in Lambda 5. The only new connective is ⊥. It
has no introduction form, and its elimination form (Rule ⊥ E) is restricted to be local as
usual. The ⊥ proposition is mobile, however (Figure 3.12).
In C5 the context Γ can contain hypotheses of the form ω world, x:A @ w, and u:A ? w.
Falsehood hypotheses are introduced by the rule bc (“by contradiction”), which corre-
sponds directly to the classical axiom (¬A ⊃ A) ⊃ A. Operationally, letcc captures
the current continuation (which expects a value of type A @ w) and binds it as a contin-
uation variable A ? w while continuing the proof of A @ w. The # rule may be alarming
at first glance because it requires the assumption A?w to appear in the conclusion. This
is because the # rule is actually the hypothesis rule for falsehood, and will have a corre-
sponding substitution principle. The rule simply states that if we have the assumption
that A is false and are able to prove that A is true at the same world, then we can de-
duce a contradiction and thus any proposition. The # rule is realized operationally as a
throw of an expression to a matching continuation. Note that continuations are global—
we can throw from any world to a remote continuation A?w, provided that we are able
to construct a proof of A @ w.
For each kind of hypothesis we have a substitution theorem.
Theorem 14 (C5 truth substitution)
If D :: Γ ` M : A @ w
and E :: Γ, x:A @ w ` N : B @ w0
then F :: Γ ` [ M/x ]N : B @ w0
34
Theorem 15 (C5 falsehood substitution)
If ∀C, ω. D :: Γ, x:A @ w ` M : C @ ω
and E :: Γ, u:A ? w ` N : B @ w0
then F :: Γ ` [[ x.M/u ]]N : B @ w0
Here, truth substitution [ M/x ]N is defined as in Lambda 5. Theorem 15, however,
warrants special attention. This principle is dual to the # rule just as Theorem 14 is
dual to hyp. The # rule contradicts an A ? w with an A @ w, so to eliminate a falsehood
assumption by substitution we are able to assume A @ ω and must produce another con-
tradiction. Reading ` as logical consequence, we have that if A false gives B, and A true
gives C (for all C), then B. This can easily be seen as a consequence of excluded middle
on A. We write this substitution as [[ x.M/u ]]N where x is a binder (with scope through M )
that stands for the value thrown to u. It is defined pointwise on N except for a use of
the # rule on u:
. 0
[[ x.M/u ]]throw N 0 to u = [ N /x ]M
This principle is close to what Parigot calls structural substitution for the λµ-calculus.
Operationally, we see this as replacing the throw with some other handler for A. Since
the new handler must have parametric type, typically it is a throw to some other con-
tinuation, perhaps after performing some computation on the proof of A.
Proof of Theorem 14 is by a trivial induction on E (it comes “for free” in the Twelf
formalization in Appendix A.4). Proof of Theorem 15 is by induction on the derivation
E, appealing to Theorem 14 in the case above. The full proof is given as the relation xs
in Appendix A.4. 2
We wish to know that our proof theory (specially constructed to give rise to a good
operational semantics) is not simply ad hoc; that it is consistent and really embodies
classical S5. To do so we prove in the next section a correspondence to a straightforward
sequent formulation of classical S5 with the subformula property. We’ll use the sequent
calculus as intuition as we develop proof terms for some classically true propositions in
Section 3.4.5, and then explore the dynamic semantics of the language in Section 3.4.6.
35
contra ⊥T
Γ, A @ w # A ? w, ∆ Γ, ⊥ @ w # ∆
Γ, A ⊃ B @ w, B @ w # ∆
Γ, A ⊃ B @ w # A ? w, ∆ Γ, A @ w # B ? w, A ⊃ B ? w, ∆
⊃ T ⊃ F
Γ, A ⊃ B @ w # ∆ Γ # A ⊃ B ? w, ∆
Γ, 2A @ w, A @ w0 # ∆ Γ, ω world # A ? ω, 2A ? w, ∆
2T 2F
Γ, 2A @ w # ∆ Γ # 2A ? w, ∆
Γ, 3A @ w, ω world, A @ ω # ∆ Γ # A ? w0 , 3A ? w, ∆
3T 3F
Γ, 3A @ w # ∆ Γ # 3A ? w, ∆
Γ # A ? w, A ∧ B ? w, ∆
Γ, A ∧ B @ w, A @ w, B @ w # ∆ Γ # B ? w, A ∧ B ? w, ∆
∧T ∧F
Γ, A ∧ B @ w # ∆ Γ # A ∧ B ? w, ∆
Γ, A at w0 @ w, A @ w0 # ∆ Γ # A ? w0 , A at w0 ? w, ∆
at T at F
Γ, A at w0 @ w # ∆ Γ # A at w0 ? w, ∆
Figure 3.13: Classical S5 sequent calculus. Left rules operate exclusively on truth hy-
potheses in Γ, and right rules operate exclusively on falsehood hypotheses in ∆. The
sequent Γ # ∆ is read as follows: given hypotheses of truth Γ and hypotheses of false-
hood ∆, we derive a contradiction. We include hypotheses about the existence of worlds
in Γ, as a convention.
decompose a connective in either the truth or falsehood context. It is best to read these
rules bottom-up, as if during proof search. For example, in the ⊥ T rule, ⊥ being true at
any world is contradictory, so we are done. In ∧ T, to reason from the hypothesis that
A ∧ B is true, we assume that A is true and B is true. In ∧ F, A ∧ B being false would
mean that either A is false or B is false, so we must show contradictions in each of those
cases. Note that 2 and 3 are dual in this presentation, and that the at connective is
self-dual.
Because each rule analyzes exactly one connective by breaking it into its compo-
nents, this sequent calculus also has the subformula property. This gives us consistency:
36
3.4.4 Soundness and completeness
We would now like to prove that our local, truth-biased natural deduction system is
equivalent to the sequent calculus. We start by proving soundness. Like the soundness
of Lambda 5 with respect to the IS5∪ sequent calculus (Theorem 11) we require a cut
lemma about the sequent calculus. For the classical sequent calculus presented in this
symmetric manner, this lemma is excluded middle:
Theorem 17 (Excluded middle)
If D:: Γ, A @ w # ∆
and E :: Γ # A ? w, ∆
then F:: Γ#∆
Proof of Theorem 17 is by lexicographic induction over the structure of the formula
A and (simultaneously) the derivations D and E. The interesting cases are when D ends
with a truth rule acting on A and E is a falsehood rule acting on A. For example, if
D. 0 E. 0
. .
Γ, 2A @ w, A @ w0 # ∆ Γ, ω world # A ? ω, 2A ? w, ∆
2T 2F
D = Γ, 2A @ w # ∆ and E = Γ # 2A ? w, ∆
E 00 :: Γ, ω world # A ? ω, ∆
D00 :: Γ, A @ w0 # ∆
F 00 :: Γ # ∆
as required.
The full proof appears in Appendix A.4 as the relation xm. 2
We also require a lemma that justifies the mobile judgment. Its statement is
Theorem 18 (Switch)
If D :: A mobile
and E :: Γ # A ? w, ∆
then F :: Γ # A ? w0 , ∆
The proof is similar to Theorem 4, proceeding by induction on D and appealing to
excluded middle. It appears as the relation switch in Appendix A.4. 2
We can now state and prove soundness of C5:
37
Theorem 19 (C5 soundness)
If D :: Γ ` M : A @ w
then F :: Γ @ # A ? w, ∆?
Γ @ selects all of the hypotheses of the form B @ w0 (and ω world) from the context, and
Γ? selects the hypotheses of the form B?w0 . Soundness states that if from some hypothe-
ses we can derive a conclusion of A being true at w, then in the sequent calculus we
can derive a contradiction from those same hypotheses, plus a hypothesis that A is false
at w. (Operationally, we can think of falsehood assumption as the “final continuation”
that takes the result of our computation.)
The proof of soundness is by induction on D. The elimination rules are straightfor-
ward because they match the F rules in the sequent calculus. The get rule appeals to
Theorem 18. Elimination rules appeal to excluded middle. Interestingly, the structural
rules bc and # simply use contraction and weakening for the sequent calculus. If
D. 0
.
Γ, u:A ? w ` M : A @ w
bc
D = Γ ` letcc u in M : A @ w
D0 :: Γ @ # A ? w, A ? w, Γ?
D. 0
.
Γ, u:A ? w ` M : A @ w
#
D = Γ, u:A ? w ` throw M to u : C @ w0
D0 :: Γ @ # A ? w, A ? w, Γ?
38
biased towards reasoning about truth rather than falsehood, the F rules are more diffi-
cult and make nontrivial use of the falsehood substitution theorem (Theorem 15). For
instance, in the ∧F case we have by induction hypothesis:
By two applications of Theorem 15, we get that the following proof term has any type
at any world: hh ii
x.[[ [Link],yi to up/ ]]N
ub
/ua N1
2
First, we form a throw of the pair hx, yi to our pair continuation up. This has free truth
hypotheses x:A and y:B. Therefore, we can use it to substitute away the ub continu-
ation in N2 (any throw of M to ub becomes a throw of hx, M i to up). Finally, we can
use this new term to substitute away ua in N1 , giving us a term that depends only on
the pair continuation up. This pattern of prepending work onto continuations through
substitution is characteristic of this proof, and reflects our bias towards the truth judg-
ment in natural deduction. As another example, in the case for the 3F rule we have by
induction hypothesis:
Simply enough, if u is ever thrown to, then we instead take that term’s address (which
lives at w0 ), move it to w, and throw it to our 3A continuation ud.
Finally, the case for 2F is interesting because it involves a letcc. By induction hy-
pothesis we have:
2
The most natural LF encoding of falsehood is 3rd -order [4]; we use a 2nd -order encoding in our proofs
(proving the falsehood substitution theorem by hand) because third-order proof checking is not available
in Twelf.
39
The rule . . . is admissible as . . .
⊥T
Γ, ¬A @ w # A ? w, ∆ Γ, A ⊃ ⊥ @ w # A ? w, ∆ Γ, A ⊃ ⊥ @ w, ⊥ @ w # ∆
¬T ⊃ T
Γ, ¬A @ w # ∆ Γ, A ⊃ ⊥ @ w # ∆
Γ, A @ w # A ⊃ ⊥ ? w, ∆
weaken-⊥
Γ, A @ w # ¬A ? w, ∆ Γ, A @ w # ⊥ ? w, A ⊃ ⊥ ? w, ∆
¬F ⊃ F
Γ # ¬A ? w, ∆ Γ # A ⊃ ⊥ ? w, ∆
Figure 3.14: The admissible rules for negation in the C5 sequent calculus and natural
deduction.
3.4.5 Examples
Before presenting the operational semantics for C5, it may be helpful to see some exam-
ple proof terms and their intended computational meaning. Because our examples use
negation (¬A), we’ll need to briefly explain how we treat it.
Negation
Although we have not given the rules for the negation connective, it is easily added
to the system. Here we equivalently take the standard shortcut of treating ¬A as an
abbreviation for A ⊃ ⊥. We computationally read ¬A @ ω as a continuation expecting A,
although this should be distinguished from primitive continuations u with type A ? ω:
the former is formed by lambda abstraction and eliminated by application, while the
latter is formed with letcc and eliminated by a throw to it. The two are related in that
we can reify a continuation assumption u:A ? ω as a negated formula ¬A by lambda
abstracting a throw to it: λ[Link] a to u. Likewise, we can get a falsehood assumption
from a term M of type ¬A, namely M (letcc u in . . .).
Finally, note that we have admissible sequent calculus rules ¬T and ¬F . Each just
flips the proposition under negation to the other side of the sequent, as expected (Fig-
ure 3.14).
40
Classical axioms
Let’s begin with the following classical equivalence
2A ≡ ¬3¬A
(In fact, in classical logic it is standard practice to define 2 this way). From left to right
the implication is intuitionistically provable, so we’ll look at the proof of the implication
from right to left. We begin with the sequent calculus proof, to show why this is clearly
true classically. We elide any residual assumptions that go unused.
contra
−, A @ ω 0 # A @ ω 0 , −
¬F
− # ¬A @ ω 0 , A @ ω 0 , −
3F
− # 3¬A @ ω, A @ ω 0 , −
2F
− # 3¬A @ ω, 2A @ ω
¬T
¬3¬A @ ω # 2A @ ω
⊃ F
# ¬3¬A ⊃ 2A @ ω
In this proof, we are using 2 F to get the hypothetical world ω 0 at which 2A is false.
From there, we can learn ¬A at the same world, which leads to a contradiction. In
natural deduction, the proof tells an interesting story:
λxd . (xd : ¬3¬A @ w)
box ω 0 . (need to return A)
letcc u in abort get[w] (applying xd will yield ⊥)
xd (get[w] (here(λ[Link] a to u)))
In each example, we’ll assume that the whole term lives at the constant world w. Op-
erationally, the reading of ¬3¬A ⊃ 2A is that given a continuation xd (expecting the
address of an A continuation), we will return a boxed A that is well-formed anywhere.
The proof term given accomplishes this by creating a box that, when opened, grabs the
current continuation u, which has type A ? ω 0 . With the continuation in hand, we travel
back to ω (where xd lives), and apply xd to the address of a function that throws to u. In
short, at the moment the box is opened we have a lack of an A, which we can grab with
letcc and then take the address of with here. This is enough to send to the continuation
that we’re provided.
Dually we can define 3 in terms of 2. Again, one direction is intuitionistically valid.
The other,
¬2¬A ⊃ 3A
is a function from a continuation to the address of some arbitrary A. It is implemented
by the following proof term:
λxc . (xc : ¬2¬A @ w)
letcc u in (u : 3A ? w)
abort( (applying xc will yield ⊥)
0
xc (box ω .λa. (a : A @ ω 0 )
throw (get[ω 0 ](here a)) to u))
41
Here, we immediately do a letcc, grabbing the 3A continuation at w. We then form
a box to pass to the continuation xc . It contains a function of type A ⊃ ⊥, which takes
the address of its argument and throws it to the saved continuation u. Thus the source
of the 3A that we ultimately return is the world that invokes the continuation (of type
¬A) that we’ve boxed up.
Excluded modal. The last example uses disjunction, which we left out of the calculus.
It can be added as a primitive connective in the same way as we did in Lambda 5 (Sec-
tion 3.2.3), or by de Morgan translation using the ¬ and ∧ connectives. In order to do
one more informal example, let’s assume the existence of proof terms inl and inr for
injecting into disjunctive type. Then we can prove the classical theorem
2A ∨ 3¬A
which is similar to the excluded middle axiom A ∨ ¬A. The proof term exhibits both the
“space travel” (moving between worlds) of modal logic and “time travel” (non-local
control flow) of classical logic. It is as follows:
42
represent local resources, however, the substitution-based Lambda 5 semantics appears
to do just this. The C5 semantics uses tables to store local resources, so that integer in-
dexes into these tables can be transmitted between worlds instead. It also uses tables for
continuations, so that we do not need to transmit control stacks. In the implementation
of ML5, we will use a similar technique to marshal certain values (Section 5.5.4), so C5’s
table-based semantics provides some of the justification for that. However, because this
semantics is awkward to formalize in Twelf (Appendix A.5), this is the last store-based
dynamic semantics that we give.
types A, B ::= A ⊃ B | 2A | 3A | A ∧ B | ⊥ | A at w
networks N ::= W; R
configs W ::= {w1 : hχ1 , b1 i, · · · }
cursors R ::= w : [k ≺ v] | w : [k M ]
tables b ::= • | b, ` = v
cont tables χ ::= • | χ, K = k
config types Σ ::= {w1 : hX1 , β1 i, · · · }
table types β ::= • | β, ` : A
ctable types X ::= • | X, K : A
world exps w ::= ω|w
world vars ω world names w
labels ` value vars x, y
cont labs K cont vars u
values v ::= λx.M | box ω.M | w.` | hv, v 0 i | held v
conts k ::= return Z | finish | abort | k f
cont exps Z ::= w.K | u
frames f ::= ◦ N | v ◦ | here ◦ | unbox ◦ | hold ◦
| letd ω.x = ◦ in N | πn ◦ | h◦, N i | hv, ◦i
| leta x = ◦ in N
exps M, N ::= v | M N | x | ` | here M | get[w] M
| unbox M | letd ω.x = M in N
| abort M | letcc u in M | hold M
| throw M to Z | hM, N i | πn M
| leta x = M in N
Figure 3.15: The syntax for the C5 operational semantics. A network consists of a set
of worlds paired with a cursor; each world has a table of values and continuations. A
cursor indicates the evaluation state of a single thread at the selected world.
For the C5 operational semantics we give require a number of new syntactic con-
structs, given in Figure 3.15. The small-step semantics is given in terms of an abstract
network that steps from state to state. The network is built out of a fixed number of con-
stant worlds, whose names we write continue to write as bold w. A network state N has
two parts. First is a world configuration W which associates two tables with each world
wi present. The first table χi stores that world’s continuations by mapping continuation
43
labels K to literal continuations k. The second table bi maps value labels ` to values in
order to store values whose address we have published (with here). These tables have
types X and β respectively (which map labels K and ` to types), and so we can likewise
construct the type of an entire configuration, written Σ.
Aside from the current world configuration, a network state also contains a cursor
denoting the current focus of computation. The cursor either takes the form w : [k ≺ v]
(returning the value v to the continuation k) or w : [k M ] (evaluating the expression
M in continuation k). In either case it selects a specific world w where the computation
is taking place.
Continuations themselves are stacks of frames (expressions with a “hole,” written
◦) with a bottommost return, finish or abort. The finish continuation represents the
end of computation, so a network state whose cursor is returning a value to finish is
called terminal. The abort continuation will be unreachable, and return will send the
received value to a remote continuation.
Most of the expressions and values are straightforward. As in Lambda 5, the canon-
ical value for 2 abstracts over the hypothetical world and leaves its argument uneval-
uated (box ω 0 .M ). For 3A, which represents the address of a value at an undisclosed
world, we no longer ship the actual value but an index into some world’s table, paired
with the name of that world. This value takes the form w.`, and is well-formed any-
where (assuming that w’s table has a label ` containing a value of type A). On the other
hand we have another sort of label, written just `, which is disembodied from its world.
These labels arise from the letd construct, which deconstructs an address w.` into its
components w and ` (see the 3 E rule from Figure 3.11). Disembodied labels only make
sense at a single world—here ` would have type A @ w. When we attempt to evaluate a
disembodied label, we look it up in the current world’s table and return the associated
value.
Although the external language only allows a throw to a continuation variable, inter-
mediate states of evaluation require that these also include the continuation expression
w.K, which pairs a continuation label (an index into the continuation table) with the
world at which it lives. These continuation expressions are filled in by letcc.
The type system is given in Figure 3.17, omitting the rules that are the same as in
Figure 3.11 except for the configuration typing Σ. There are several judgments involved,
an index of which appear in Figure 3.16.
The rules addr and lab are used to type the run-time artifacts of address publishing.
In either case, we look up the type in the appropriate table typing β, which is part of the
configuration type Σ. As mentioned, throw allows a continuation expression Z, which
must take the form of a variable (typed with hyp?, as in the logic) or address into a
continuation table.
Typing of literal continuations k is straightforward. Note that the judgment Σ `
k : A ? w means that the continuation k expects a value of type A at w. The return
continuation arises from get, so it allows values of mobile type to be returned. We reuse
the network continuation mechanism here to refer to the outstanding get on the remote
machine.
For an entire network to be well-formed (Rule net), all of the tables must have the
44
Judgment Reading
Figure 3.16: Index of Judgments for the C5 operational semantics. In each judgment Σ is
a configuration typing and Γ is a context of value, world and continuation hypotheses.
type indicated by the configuration type Σ, which means that they must have exactly
the same labels, and the values or continuations must be well-typed at the specified
types (Rules b and χ). Finally, the cursor must be well-formed: It must select a world
that exists in the network, and there must exist a type A such that its continuation and
value or expression both have type A at that world and are closed.
Having set up the syntax and type system, we can now give the evaluation relation
and prove a type safety theorem for it.
Evaluation relation
45
Σ(w) = hX, βi β(`) = A Σ(w) = hX, βi β(`) = A
addr lab
Σ; Γ ` w.` : 3A @ w0 Σ; Γ ` ` : A @ w
Σ; Γ ` M : A @ w Σ; Γ ` Z : A ? w Σ; Γ, u:A ? w ` M : A @ w
throw letcc
Σ; Γ ` throw M to Z : C @ w0 Σ; Γ ` letcc u in M : A @ w
Σ; Γ ` v : A @ w0
held ka kf
Σ; Γ ` held v : A at w0 @ w Σ ` abort : ⊥ ? w Σ ` finish : A ? w
A mobile Σ; · ` Z : A ? w0 Σ ` k : 3A ? w
kret khere
Σ ` return Z : A ? w Σ ` k here ◦ : A ? w
Σ ` k : B ? w Σ; · ` N : A @ w Σ ` k : B ? w Σ; · ` v : A ⊃ B @ w
kapp1 kapp2
Σ ` k ◦ N : A ⊃ B ?w Σ ` k v ◦ : A?w
Σ ` k : A ∧ B ? w Σ; · ` N : B @ w Σ ` k : A ∧ B ? w Σ; · ` v : A @ w
kpair1 kpair2
Σ ` k h◦, N i : A ? w Σ ` k hv, ◦i : B ? w
Σ ` k : A at w ? w Σ ` k : C ? w Σ; x:A @ w0 ` N : C @ w
khold kleta
Σ ` k hold ◦ : A ? w Σ ` k leta x = ◦ in N : A at w0 ? w
β = (`1 : A1 , . . .) Σ; · ` v1 : A1 @ w . . . X = (K1 : A1 , . . .) Σ ` k1 : A1 ? w . . .
b χ
{· · · , w : hX, βi, · · · } ` `1 = v1 , . . . @ w {· · · , w : hX, βi, · · · } ` K1 = k1 , . . . ? w
| {z } | {z } | {z } | {z }
Σ b Σ χ
w ∈ dom(Σ) w ∈ dom(Σ)
Σ; · ` v : A @ w Σ ` k : A ? w Σ; · ` M : A @ w Σ ` k : A ? w
ret eval
Σ ` w : [k ≺ v] Σ ` w : [k M ]
Σ ` R Σ ` χi @wi . . . Σ ` bi @wi . . .
net
Σ ` {w1 : hχ1 , b1 i, · · · , wm : hχm , bm i}; R
Figure 3.17: The C5 type system, with rules for typing network states, continuation
expressions, tables, cursors, etc.
46
⊃e -p W; w : [k M N ] 7→ W; w : [k ◦N M ]
⊃e -s W; w : [k ◦N ≺ v] 7→ W; w : [k v ◦ N ]
⊃e -r W; w : [k (λx.M )◦ ≺ v] 7→ W; w : [k [v/x]M ]
∧i -p W; w : [k hM, N i] 7→ W; w : [k h◦, N i M ]
∧i -s W; w : [k h◦, N i ≺ v] 7→ W; w : [k hv, ◦i N ]
∧i -r W; w : [k hv1 , ◦i ≺ v2 ] 7→ W; w : [k ≺ hv1 , v2 i]
∧en -p W; w : [k πn M ] 7→ W; w : [k πn ◦ M ]
∧en -r W; w : [k πn ◦ ≺ hv1 , v2 i] 7→ W; w : [k ≺ vn ]
2i -v W; w : [k box ω.M ] 7→ W; w : [k ≺ box ω.M ]
3i -v W; w : [k w0 .`] 7→ W; w : [k ≺ w0 .`]
⊃i -v W; w : [k λx.M ] 7→ W; w : [k ≺ λx.M ]
∧i -v W; w : [k hv1 , v2 i] 7→ W; w : [k ≺ hv1 , v2 i]
ati -v W; w : [k held v] 7→ W; w : [k ≺ held v]
⊥e -p W; w : [k abort M ] 7→ W; w : [abort M ]
ati -p W; w : [k hold M ] 7→ W; w : [k hold ◦ M ]
ati -r W; w : [k hold ◦ ≺ v] 7→ W; w : [k ≺ held v]
ate -p W; w : [k leta x = M in N ] 7→ W; w : [k leta x = ◦ in N M ]
ate -r W; w : [k leta x = ◦ in N ≺ held v] 7→ W; w : [k [v/x]N ]
3i -p W; w : [k here M ] 7→ W; w : [k here ◦ M ]
3i -r {w : hχ, bi, · · · }; w : [k here ◦ ≺ v] 7→ {w : hχ, (b, ` = v)i, · · · }; w : [k ≺ w.`]
(` fresh)
`-r {w : hχ, bi, · · · }; w : [k `] 7→ {w : hχ, bi, · · · }; w : [k ≺ v]
(b(`) = v)
3e -p W; w : [k letd ω.x = M in N ] 7→ W; w : [k letd ω.x = ◦ in N M ]
3e -r W; w : [k letd ω.x = ◦ in N ≺ w0 .`] 7→ W; w : [k [`/x][w0 /ω]N ]
2e -p W; w : [k unbox M ] 7→ W; w : [k unbox ◦ M ]
2e -r W; w : [k unbox ◦ ≺ box ω.M ] 7→ W; w : [k [w/ω]M ]
letcc {w : hχ, bi, · · · }; w : [k letcc u in M ] 7 → {w : h(χ, K = k), bi, · · · }; w : [k [w.K/u]M ]
(K fresh)
throw {w0 : hχ, bi, · · · }; w : [k throw M to w0 .K] 7→ {w0 : hχ, bi, · · · }; w0 : [k 0 M ]
(χ(K) = k 0 )
0 0
get {w : hχ, bi, · · · }; w : [k get[w ] M ] 7→ {w : h(χ, K = k), bi, · · · }; w : [return w.K M ]
(K fresh)
ret {w : hχ, bi, · · · }; w0 : [return w.K ≺ v] 7→ {w : hχ, bi, · · · }; w : [k ≺ v]
(χ(K) = k)
47
tables locally.
The rules for 2 are much simpler: box ω.M is already a value (Rule 2i -v), and to
unbox we simply substitute the current world for the hypothetical one (Rule 2e -r).
When encountering a letcc, we grab the current continuation k. Because the con-
tinuation may be referred to from elsewhere in the network, we publish it in a table
and form a global address for it (of the form w.K), just as we did for 3 addresses. This
address is substituted for the falsehood variable u using standard substitution—not the
special falsehood substitution (Theorem 15) we used in Section 3.4.2. The latter was a
proof-theoretic notion used to eliminate uses of the hypothesis; here we want the use of
the hypothesis (throw) to have run-time significance. A point of comparison is the above
paragraph, where we substituted the expression ` for a variable because we wanted to
delay the operation until the time the variable is “looked up.”
Throwing to a continuation (Rule throw) is handled straightforwardly. The continu-
ation expression will be closed, and therefore of the form w0 .K. We look up the label K
in w0 —or rather, cause w0 to look it up—and pass the expression M to it. Note that we
do not evaluate the argument before throwing it to the remote continuation. In general
we can not evaluate it, because it is only well-typed at the remote world, which may be
different from the world we’re in.
Finally, the get construct works as follows. Since the target world expression must
be closed it will be a world constant in the domain of W. We will move the cursor to
that world and begin evaluating the expression M . To arrange for the result of M to
be returned to us, we insert our current continuation in the local continuation table.
The bottom frame at the remote world is then a return to that continuation label. The
return frame reduces like throw. Unlike throw, its argument (restricted to mobile types)
will be eagerly evaluated—the whole point is to create the value at one world and then
move it to another.
Type safety
In order for C5 to make sense it must be type safe: any well-typed program must have
a meaning, given as a sequence of steps in the abstract network. For the small-step
semantics, we state type safety in terms of progress and preservation:
Theorem 21 (C5 Progress)
If Σ`N
then either N is terminal
or ∃N0 .N 7→ N0
Theorem 22 (C5 Preservation)
If Σ`N
and N 7→ N0
then ∃Σ0 . Σ0 ⊇ Σ
such that Σ0 ` N0
Progress says that any well-formed network state can take another step, or is done.
(Recall that a terminal network is one where the cursor is returning a value to a finish
48
continuation.) Preservation says that any well-typed network state that takes a step
results in another well-typed state (perhaps in an extended configuration typing Σ0 .
(Σ0 ⊇ Σ iff Σ0 and Σ each describe the same set of worlds, and for each world, if X 0 (K) =
A then X(K) = A, and likewise for β 0 and β). By iterating alternate applications of these
theorems we see that any well-typed program is able to step repeatedly and remain
well-formed, or else eventually comes to rest in a terminal state.
The proof of Theorem 21 is by induction over the derivation that N is well-formed,
and proof of Theorem 22 is by induction over the derivation that N 7→ N0 . These proofs
are somewhat difficult to perform in Twelf due to the value/continuation tables; be-
cause they are indexed by labels (and not variables) we cannot use Twelf’s facilities for
higher order abstract syntax to encode them. This means that we must prove substitu-
tion and weakening theorems by hand. The full proofs appear in Appendix A.5 as the
relations progress and preservation. 2
Uses of continuations
This cursor-based style of operational semantics admits an easy extension to support
concurrency. We simply replace the cursor R in our network state (W; R) with a multiset
of cursors <, each one represent an independent thread. We then permit a step on any
one of these cursors essentially according to the old rules. Formally,
W; < 7→c W0 ; <0
iff < = R ] <rest
and W; R 7→ W0 ; R0
and <0 = R0 ] <rest
We can then add primitives as desired to spawn new cursors. A very simple one
evaluates M and N in parallel and returns each one to the same continuation:
Γ ` M : A @ w Γ ` N : A @ w par
Γ ` M |N : A @ w
W; < ] w:[k M |N ] 7→c W; < ] w:[k M ] ] w:[k N ]
It is easy to see that suitable extensions of progress and preservation hold for 7→c .
In ML5, we implement concurrency in the same way. In interactive applications,
threads are inserted into the pool as a result of actions by the user such as keypresses
and mouseclicks on GUI elements. The interface to the database also allows a hook to
be registered with a key, so that the hook is launched as a new thread whenever the key
is modified.
This concludes the discussion of C5, a classical modal logic for distributed com-
puting. We will use continuations again starting in Chapter 4 when we formalize the
compilation of Lambda 5 via CPS conversion. ML5 also has the letcc and throw con-
structs, although there they will be limited to (spatially) local control flow (Chapter 5).
In the next section, we will look at an orthogonal extension to Lambda 5 for universal
reasoning.
49
3.5 Validity
In our modal calculi so far, reasoning has always been attached to a specific world via
the “true @ w” (or “false ? w”) judgments. This is the critical technology in this work
because it allows us to express contingent truths, which correspond to programs that
are limited in where they may be executed. Nonetheless, distributed programs usually
contain a large fraction of data and code that can be used anywhere: simple data like in-
tegers, and library code that manipulates data structures like lists and trees, etc. We call
such unconstrained data and code “valid.” For these parts of the program, the modal
typing discipline is a burden, because it requires us to explicitly move valid things from
world to world. For example, the following Lambda 5 program of int ⊃ 2int @ w is not
as direct as we would like:
λx:int. box ω.get[w] x
The problem is that the box that we create must return to w every time it is unboxed, in
order to retrieve x. This is more work than should be necessary; the box ought to simply
contain the integer. Because of the modal discipline, however, there is no way for us
to make x available for use at the hypothetical world ω, because we cannot mention
the bound world variable until we introduce it with the box construct. This turns out
to be particularly troublesome when we introduce run-time addresses for worlds in
Section 4.2.1. When we begin writing the body of a box at some world where truly
nothing is known, then we won’t even have the ability to get data from other worlds,
since we will not have access to their run-time addresses!
The solution is to introduce universal reasoning into the calculus. This will allow
us to assert that a piece of code or data is valid and can be used anywhere—including
hypothetical worlds that are introduced later. We will make such assertions in terms of
a new validity judgment, and introduce a modality that internalizes this judgment as a
type.
In this development we will see a widening gap between the logic and the program-
ming language based on it. Specifically, we will make a syntactic constraint similar to
ML’s value restriction [77] on polymorphism so that we always have a specific con-
crete place in which we are performing evaluation. This leads to an incompleteness of
the call-by-value dynamic semantics with respect to the proof theory. (Like ML’s value
restriction, this is rarely a problem in practice, because we can almost always lambda-
abstract something to make it a value.)
To this end, we only form hypotheses of validity, never conclusions. Operationally,
a hypothesis tells us that we have a value and tells us where we may use that value—a
valid hypothesis simply says that we can use it anywhere. The hypothesis form is
u ∼ ω.A
where u is a new syntactic class of variable. The world variable ω is bound within A and
stands for the world(s) at which u is used. It rarely occurs, so we leave it out when it
does not (or can not). Valid variables can be used at any world to conclude modal truth
at that world (Rule vhyp; Figure 3.19).
50
A mobile Γ ` M : A @ w Γ, u ∼ A ` N : C @ w
vhyp put
Γ, u ∼ ω.A ` u : [ w/ω ]A @ w Γ ` put u = M in N : C @ w
Γ, ω world ` M : A @ ω Γ ` M : ω A @ w Γ, u ∼ ω.A ` N : C @ w
I E
Γ ` sham ω.M : ω A @ w Γ ` letsham u = M in N : C @ w
M
ωA mobile
51
Γ, A ⊃ B @ w =⇒ A @ w
Γ, A ⊃ B @ w, B @ w =⇒ C @ w0 Γ, A @ w =⇒ B @ w
⊃ L ⊃ R
Γ, A ⊃ B @ w =⇒ C @ w0 Γ =⇒ A ⊃ B @ w
Γ, A at w @ w0 , A @ w =⇒ C @ w00 Γ =⇒ A @ w
at L at R
Γ, A at w @ w0 =⇒ C @ w00 Γ =⇒ A at w @ w0
0
Γ, ωA @ w , ∼ ω.A =⇒ C @ w Γ, ω world =⇒ A @ ω
L R
Γ, ω A @ w0 =⇒ C @ w Γ =⇒ ω A @ w
>R ⊥L
Γ =⇒ > @ w Γ, ⊥ @ w =⇒ C @ w0
0
Γ, ∼ ω.A, [ w /ω ]A @ w0 =⇒ C @ w
copy init
Γ, ∼ ω.A =⇒ C @ w Γ, A @ w =⇒ A @ w
Figure 3.20: A miniature sequent calculus with validity and the modality. Here,
hypotheses are not labeled with variable names, so a valid hypothesis takes the form
“∼ ω.A”.
system with very few connectives, since the reasoning for the remainder would be the
same as it was for Lambda 5. The sequent calculus appears in Figure 3.20.
The rules for ⊃ and at are the same as before, as is the initial sequent. The rules
for the modality are straightforward; the right rule mimics the introduction rule from
natural deduction. The left rule produces a validity hypothesis. To use a validity hy-
pothesis, we choose a world and apply the copy rule to create a modal hypothesis of
the same formula at that world (with the target world substituted in for the hypotheti-
cal one). This is analogous to the copy rule used in Chang, Chaudhuri, and Pfenning’s
sequent calculus for linear logic [17]. After creating a modal hypothesis it can be used
like any other with the init rule.
Like the previous two sequent calculi, this one has the subformula property and so
it is easy to see consistency. We also have shift and expansion lemmas:
Theorem 24 (Consistency)
Not all sequents are provable.
Theorem 25 (Expansion)
If A mobile
then A @ w =⇒ A @ w0
Theorem 26 (Shift)
If A mobile
and Γ =⇒ A @ w
then Γ =⇒ A @ w0
Proof of Theorem 24 is immediate by witness (no rule can conclude · =⇒ ⊥ @ w), and
Theorems 25 and 26 proceed as Theorems 3 and 4 for Lambda 5, extended straightfor-
52
wardly to the new connective. These appear as the Twelf relations shift and exp in
Appendix A.6. 2
For this sequent calculus there are two cut principles, one for each form of hypothe-
sis:
Theorem 27 (Cut)
If D :: Γ, Γ0 =⇒ A @ w
and E :: Γ, A @ w, Γ0 =⇒ B @ w0
then F :: Γ, Γ0 =⇒ B @ w0
Theorem 28 (Valid cut)
If D :: Γ, Γ0 =⇒ A @ ω (for all ω)
and E :: Γ, ∼ ω.A, Γ =⇒ B @ w0
0
then F :: Γ, Γ0 =⇒ B @ w0
Proof of Theorems 27 and 28 is by mutual lexicographic induction over the size of
the cut formula A, then simultaneously the derivations D and E. We define the size
of A such that the parameterized proposition ω.A is larger than [ w/ω ]A for any w. This
relies on worlds not having any inductive structure. The interesting case for Cut is the
principal cut for the connective. If
D. 0 E. 0
. .
Γ, ω world =⇒ A @ ω Γ, ω A @ w0 , ∼ ω.A =⇒ C @ w
R L
D = Γ =⇒ ω A @ w and E = Γ, ω A @ w0 =⇒ C @ w
E :: Γ, ∼ ω.A =⇒ C @ w
then by inductive appeal to Valid cut on D0 and E 0 (justified by the smaller cut formula)
we have
F :: Γ =⇒ C @ w
as required.
The cases for Valid cut generally mimic the ones for Cut; there are no principal cases
since none of the left rules can operate directly on a valid hypothesis. Therefore, we
also never need to distinguish cases on the rule that concludes D. The interesting case
is when we copy the cut formula; if
E. 0
0
.
Γ, ∼ ω.A, [ w /ω ]A @ w0 =⇒ C @ w
copy
E = Γ, ∼ ω.A =⇒ C @ w
53
To eliminate this hypothesis, we first observe that since D is parametric in its world,
0 0
[ w /ω ]D :: Γ =⇒ [ w /ω ]A @ w0
0 0
and so by an appeal back to Cut on [ w /ω ]D and E 0 (justified by the fact that [ w /ω ]A is
smaller than ω.A by our metric) we have
F :: Γ =⇒ C @ w
as required. The full proof appears as the relations cut and vcut in Appendix A.6. 2
Given Cut, we can prove soundness and completeness in the typical way:
Theorem 29 (Soundness of validity extensions)
If D :: Γ ` M : A @ w
then E :: p Γq =⇒ A @ w
Theorem 30 (Completeness of validity extensions)
If D :: Γ =⇒ A @ w
then E :: Γ0 ` M : A @ w, for some M and Γ0 A Γ
These proofs follow the same structure as Theorems 5 and 6 for Lambda 5. It is
interesting to note that we completely eliminate the put construct when translating
to the sequent calculus and do not use it when translating back to natural deduction;
therefore it is an admissible rule. The full proofs appear in Appendix A.6 as the relations
ndseq, uhyp, seqnd, hypnd, and vhypnd. 2
54
values v ::= λ x.M | held v | sham ω.v | x | u
hyp vhyp
Γ, x:A @ w ` x : A @ w Γ, u ∼ ω.A ` u : [ w/ω ]A @ w
Γ, x:A @ w ` M : B @ w Γ ` M : A ⊃ B @w Γ ` N : A@w
⊃ V ⊃ E
Γ ` λ x.M : A ⊃ B @ w Γ ` M N : B @w
Γ ` v : A @ w0 Γ, ω world ` v : A @ ω
at V V
Γ ` held v : A at w0 @ w Γ ` sham ω.v : ω A @ w
Γ ` v : A@w Γ ` M : ω A @ w Γ, u ∼ ω.A ` N : C @ w
val E
Γ ` val v : A @ w Γ ` letsham u = M in N : C @ w
A mobile Γ ` M : A @ w Γ, u ∼ A ` N : C @ w 0
put A mobile Γ0 ` M : A @ w get
Γ ` put u = M in N : C @ w Γ ` get[w ] M : A @ w
55
0
M ⇓w λ x.M 0 N ⇓w v 0 [ v /x ]M 0 ⇓w v
app ⇓
M N ⇓w v
0
M ⇓w v M ⇓w held v 0 [ v /x ]N ⇓w v
hold ⇓ leta ⇓
hold M ⇓w held v leta x = M in N ⇓w v
M ⇓w 0 v M ⇓w v |[ ω.v/u ]|w N ⇓w v 0
get ⇓ put ⇓
get[w0 ] M ⇓w v put u = M in N ⇓w v 0
Figure 3.22: Big-step dynamic semantics for the miniature validity calculus, given as a
relation M ⇓w v between an expression M and its value v.
56
by inversion on this we have
E100 :: ω world ` v : A @ ω
F2 :: · ` |[ ω.v/u ]|w N : C @ w
F :: · ` v 0 : C @ w
as required.
The case for put is similar but more subtle because we must derive the validity of a
value from the mobility of its type. Our strengthened value shift lemma allows us to do
this. If
E.1 E.2
. .
M ⇓w v |[ ω.v/u ]|w N ⇓w v 0
put ⇓
E = put u = M in N ⇓w v 0
then by inversion on D we have
E10 :: · ` v : A @ w
as before. To use valid substitution, we need that v is well typed at any world. We can
use the stronger value shift lemma to do this. We first weaken E10 to include a hypothet-
ical world in its context, giving
E100 :: ω world ` v : A @ w
and then apply value shift, with the destination being that hypothetical world, giving
E1000 :: ω world ` v : A @ ω
57
3.5.4 Relationship with other connectives
The connective is related to—but distinct from—other connectives appearing in this
work and others. The most obvious relative is 2, which also encodes universal reason-
ing. In fact, 2A implies A and vice versa, in the proof theory. There are a number
of differences, however. First, in the lambda calculus 2A does not imply A, because
of its restriction to values. Secondly, the fact that the bound world variable ω may ap-
pear in the type (because of the hybrid connective A at w) means that ω is more precise
than 2. This means that when the variable appears, we can’t even state the equiva-
lence ω A = 2A—let alone prove it—because A mentions a variable not in scope. The
bound variable does not occur often in programs, but is important in the internals of the
compiler.
Another question is whether the elimination form for 2 ought to use the validity
judgment like the elimination form for . This is what Jia and Walker do [62] and it
would be sound in our proof theory as well. However, the body of a box must be an
expression for it to be the 2 connective, and we do not want to have to substitute ex-
pressions for valid variables (Section 3.5.2). Therefore we prefer to use the formulation
where each use of 2A is an explicit unbox that evaluates the expression when it is un-
boxed. This leaves the logical meaning of 2 intact in our call-by-value language.
The shamrock modality was inspired by Park’s connective [100, 101]. A value of
type A is a suspended computation that, when evaluated, results in a value that is
portable to any world.3 The modality is defined in terms of a validity judgment, but un-
like the validity judgment here, there are structured conclusions of validity. Validity can
be concluded by (possibly effectful) expressions of primitive type (which corresponds to
our mobile judgment and the put construct) or by a small language of pure expressions.
This means that A itself would not be mobile in our setting, since the evaluation of its
contents can have effects. The proof theory uses a form of leftist substitution where the
substitution operation is defined inductively on the term being substituted, which does
not appear to admit a straightforward compilation strategy. It is not known whether
the logic containing is globally sound and complete (equivalent to a sequent calculus
with the subformula property and cut). In comparison, we treat as an encapsulated
value that is already portable to every world, and use a form of substitution that can be
readily implemented using standard compiler techniques.
Finally, note that we cannot achieve the effect of validity by adding new types or
by using polymorphism. For example, if we had quantification over worlds (which
will be introduced in the next chapter) this would not be sufficient. Suppose we had
a function f that we want to be able to use anywhere. We cannot simply bind it as a
modal hypothesis f :∀ω.int ⊃ int @ ω. This judgment is ill-formed because the scope of
the quantifier does not include @ ω, which is part of the judgment, not the type. Using the
type f :∀ω.(A at ω) also does not work, because the modal judgment requires us to place
f at some specific world and then move it around explicitly. Therefore, we really need
a new judgment.
3
He formerly called this the fmodality, which is why it appears that way in my Thesis Proposal [86]
and other places.
58
3.6 Summary
In this chapter I have presented a series of logics and calculi that form the theoretical
basis for the programming language ML5. For each proof theory I have demonstrated
its logical status by proving it equivalent to a known logic, or to a natural sequent cal-
culus with the subformula property. Each logic is accompanied by a lambda calculus
derived from its proof terms. Although these lambda calculi are not always as strong
(or direct) as the proof theory, we make such concessions with our eyes open, and justify
the gap in terms of a dynamic semantics that suggests a straightforward path towards
implementation on a distributed set of hosts. For each lambda calculus we provide a
type safety result. Every proof has been formalized in a machine-checkable form.
We now shift gears slightly. In the next chapter, I investigate the process of compil-
ing a small programming language based on the calculi in this chapter. I do so in the
abstract, defining the compilation relations only for simple language features, without
any regard to convenience, efficiency or other implementation concerns. Each of these
compilation passes is performed in Twelf, with formalized type safety and type preser-
vation results. However, we do not seek out connections with logic in this chapter. Once
I have justified the major phases of compilation for this little language, I then present
the full-scale programming language ML5 in Chapter 5, at which time I will be mainly
concerned with pragmatic and not theoretical issues.
59
60
Chapter 4
61
eral good reasons to include it; for one, it is a natural result of using an explicit worlds
formulation of modal logic, because it is a direct internalization of the @ judgment. This
makes it not seem very exotic, since such internalizations are commonplace: For ex-
ample, in propositional logic the ⊃ connective is an internalization of the ` judgment
for hypothetical reasoning. Related to this point, we will see that the at modality is
necessary for closure conversion in Section 4.7.
From a high-level programming point of view, at is useful because it is very precise.
To illustrate, consider the following theorem of IS5:
2(A ⊃ B) ⊃ 3A ⊃ 3B
The reading of this proposition is as follows: Given that A implies B in every world,
and given that A is true in some world, B is true in some world. This proposition is true
by virtue of the fact that B is true at the same world that A is true (we go to that world
and apply the mobile function from A to B). However, the proposition does not state
that B is true at the same world; in fact, we unable to state such a thing with the 2 and
3 connectives at all. In this sense the 2 and 3 connectives, being unable to mention
worlds, are “lossy.”
The at modality allows us to make such statements. The MinML5 internal language
introduces quantification over worlds, so we will be able to say
2(A ⊃ B) ⊃ ∀ω.(A at ω) ⊃ (B at ω)
which is stronger than the above. In fact, in the logic the 2 and 3 modalities are defin-
able in terms of at and quantification:
2A = ∀ω.A at ω
3A = ∃ω.A at ω
In the translation that follows, we will eliminate 2 and 3 by using at and quantifi-
cation over worlds (the translation will be a bit more complicated than the above due to
other operational concerns), and then not consider them again.
4.2.1 Addresses
The worlds in the typing judgment are the static representations of hosts in the network.
At runtime, we will need tokens with which to refer to these worlds. A value of type
62
variables x, y
valid variables u
valid values s ::= ω.v | u
expressions M, N ::= here M | M N | leta x = M in N | lets u = M in N |
letd ω, x, y = M in N | val v | unbox M |
put u = M in N | get[M ] N | hM, N i
values v ::= λ x.M | held v | sham ω.v | vval s | x
there[w, v] | box ω.M | h v1 , v2 i
w addr is such a token, which acts as the address of a host. It is a singleton type; there
is only one value of type w addr for a given w. A host can compute its own address by
using the localhost() primitive, which results in an address value, written w:
localhost address
Γ ` localhost() : w addr @ w Γ ` w : w addr @ w0
The way an address is used is by switching the location of evaluation to the world
described by the address, using get. Thus, get now takes a new argument, which is
the address of the remote world:
A mobile
Γ ` M : w0 addr @ w
Γ ` N : A @ w0
get
Γ ` get[M ]N : A @ w
Addresses are themselves mobile. The put construct does not need an address, because
it does not communicate with a remote host. However, the elimination form for 3 now
binds both the static world variable and its address; otherwise, we would be unable
to contact that world in the body. The introduction form of 2 does not need to be
changed, because its body can use localhost() to find out where it is being evaluated,
if desired.
Γ ` s ∼ ω.A
63
Γ ` v : A@w
val
Γ ` val v : A @ w
i ∈ {1, 2} Γ ` M : A1 ∧ A2 @ w Γ ` M1 : A @ w Γ ` M2 : B @ w
∧ Ei ∧I
Γ ` #i M : Ai @ w Γ ` hM1 , M2 i : A ∧ B @ w
Γ ` M : A ⊃ B @w Γ ` N : A@w
⊃ E
Γ ` M N : B @w
Γ ` M : 2A @ w Γ ` M : A@w
2E 3I
Γ ` unbox M : A @ w Γ ` here M : 3A @ w
Γ ` M : ω A @ w Γ, u ∼ ω.A ` N : C @ w
E
Γ ` letsham u = M in N : C @ w
A mobile A mobile
Γ ` M : A@w Γ ` M : w0 addr @ w
Γ, u ∼ A ` N : C @ w Γ ` N : A @ w0
put get
Γ ` put u = M in N : C @ w Γ ` get[M ] N : A @ w
localhost
Γ ` localhost() : w addr @ w
Figure 4.2: The MinML external language typing rules for expressions
64
hyp vhyp
Γ, x:A @ w ` x : A @ w Γ, u ∼ ω.A ` u ∼ ω.A
Γ ` s ∼ ω.A
address vval
Γ ` w : w addr @ w0 Γ ` vval s : [ w/ω ]A @ w
Γ ` v1 : A @ w Γ ` v2 : B @ w Γ, x:A @ w ` M : B @ w
∧ Iv ⊃ I
Γ ` h v1 , v2 i : A ∧ B @ w Γ ` λ x.M : A ⊃ B @ w
Γ ` v : A @ w0 Γ, ω world ` M : A @ ω
at Iv 2I
Γ ` held v : A at w0 @ w Γ ` box ω.M : 2A @ w
Γ ` w : w addr Γ ` v : A @ w 3 I Γ, ω world ` s : A @ ω
v I
Γ ` there[w, v] : 3A @ w Γ ` sham ω.s : ω A @ w
Figure 4.3: The MinML external language typing rules for values and valid values
for the well-formedness of valid values. The rules for valid values and modal values ap-
pear in Figure 4.3. Valid values are values and values are expressions, via the inclusions
vval and val. The typing rules for expressions appear in Figure 4.2.
65
variables x, y
valid variables u
valid values s ::= ω.v | u
expressions M, N ::= M N | leta x = M in N | lets u = M in N |
let x = M in N | val v | M hwi | unpack ω, x = M in N |
put u = M in N | get[M ] N
values v ::= Λω.M | λ x.M | pack w, v as ∃ω.A | held v |
h i | h v1 , v2 i | sham ω.v | vval s | x
Γ ` v : A@w i ∈ {1, 2} Γ ` M : A1 ∧ A2 @ w
val ∧ Ei
Γ ` val v : A @ w Γ ` #i M : Ai @ w
Γ ` M : A ⊃ B @w Γ ` N : A@w Γ ` M : A @ w Γ, x:A @ w ` N : C @ w
⊃ E let
Γ ` M N : B @w Γ ` let x = M in N : C @ w
Γ ` M : ∀ω.A @ w
0 ∀E
Γ ` M hw0 i : [ w /ω ]A @ w
Γ ` M : ω A @ w Γ, u ∼ ω.A ` N : C @ w
E
Γ ` letsham u = M in N : C @ w
A mobile A mobile
Γ ` M : A@w Γ ` M : w0 addr @ w
Γ, u ∼ A ` N : C @ w Γ ` N : A @ w0
put get
Γ ` put u = M in N : C @ w Γ ` get[M ] N : A @ w
localhost
Γ ` localhost() : w addr @ w
Figure 4.5: The MinML internal language typing rules for expressions
66
hyp Γ ` s ∼ ω.A
vval
Γ, x:A @ w ` x : A @ w Γ ` vval s : [ w/ω ]A @ w
Γ ` v1 : A @ w Γ ` v2 : B @ w Γ, x:A @ w ` M : B @ w
∧I ⊃ I
Γ ` h v1 , v2 i : A ∧ B @ w Γ ` λ x.M : A ⊃ B @ w
Γ ` v : A @ w0 Γ, ω world ` v : A @ w
at I ∀I
Γ ` held v : A at w0 @ w Γ ` Λω.v : ∀ω.A @ w
Γ ` v : [ w/ω ]A @ w
address ∃I
Γ ` w : w addr @ w0 Γ ` pack w, v as ∃ω.A : ∃ω.A @ w
Γ, ` s ∼ ω.A
unit I I
Γ ` h i : unit @ w Γ ` sham s : ω A @ w
Γ, ω world ` v : A @ ω
vhyp valid
Γ, u∼ω.A ` u ∼ ω.A Γ ` ω.v ∼ ω.A
Figure 4.6: The MinML internal language typing rules for values and valid values.
ωM addr M at M
ωA mobile w addr mobile A at w mobile
unit M
unit mobile
Figure 4.7: The definition of the mobile judgment for the MinML5 internal language.
67
of A. To use a term of type ∃ω.A, we bind the hypothetical world ω, and a value of type
A. Note that this unpack construct does not bind an address for the abstract world, so
our translation from letd will need to do that manually.
Γ ` e : A?w
states that the evaluation context e is well-formed and expects a value of type A in the
world w. A machine m is then
e |w M
and is well-formed (in the empty context) if its evaluation context and expression agree:
· ` e : A?w · ` M : A@w
machine
· ` e |w M ok
The evaluation relation 7→w is a relation between two machines, indexed by the
world at which evaluation takes place. It is defined in Figure 4.9.
Rules roughly fall into two categories. The first is where the machine state consists of
an evaluation context and unevaluated expression, in which case we extend the evalua-
tion context and begin evaluating the expression. The second is where the machine state
consists of an evaluation context and a value appropriate for that context (via the val
inclusion), in which case we perform the reduction and continue. For localhost(),
we extract the world from the machine state (this assumes that every world can com-
pute its own address, which is reasonable) and return it. We use this same ability in
the rule for get to supply the return frame with the address of the world it should
return to.
The get and put use a partial function lift to make a value of mobile type into a
valid value (so these rules only apply when lift is defined). The definition of lift appears
in Figure 4.10. It is a straightforward induction that adds an unused world argument,
except in the case where the value is actually a vval, in which case we instantiate it at
the current world and then lift that value instead.
The final rule deserves some attention. If we have a valid value being used as a
value (via the inclusion vval), then we can instantiate it with the current world to get a
regular value. We are only forced to use this rule when we otherwise require a specific
form of value (for example in the reduction rule for leta); otherwise, we can delay
its application. This causes minor trouble in the proof of safety because it means that
we have multiple canonical forms for a given type: for A ⊃ B we have λ x.M and
vval ω.v where v is a canonical form for A ⊃ B. (On the other hand, we do not have
68
evaluation
contexts e ::= finish | e; get[◦] M | e; return[w] ◦ | e; put u = ◦ in N |
e; ◦ N | e; v ◦ | e; ◦hwi | e; #1 ◦ | e; #2 ◦ |
e; leta x = ◦ in N | e; let x = ◦ in N |
e; lets u = ◦ in N | e; unpack ω, x = ◦ in N
finish
Γ ` finish : A ? w
A mobile Γ ` e : C ? w Γ, u∼A ` N : C @ w
put
Γ ` e; put u = ◦ in N : A ? w
Γ ` e : B ? w Γ ` N : A @ w app Γ ` e : B ? w Γ ` v : A ⊃ B @ w app
1 2
Γ ` e; ◦ N : A ⊃ B ? w Γ ` e; v ◦ : A ? w
0
Γ ` e : [ w /ω ]A ? w
wapp
Γ ` e; ◦hw0 i : ∀ω.A ? w
Γ ` e : A?w #1 Γ ` e : B ?w #2
Γ ` e; #1 ◦ : A ∧ B ? w Γ ` e; #2 ◦ : A ∧ B ? w
Γ ` e : C ? w Γ, x:A @ w0 ` N : C @ w
leta
Γ ` e; leta x = ◦ in N : A at w0 ? w
Γ ` e : C ? w Γ, x:A @ w ` N : C @ w
let
Γ ` e; let x = ◦ in N : A ? w
Γ ` e : C ? w Γ, u∼ω.A ` N : C @ w
lets
Γ ` e; lets u = ◦ in N : ω A ? w
Γ ` e : C ? w Γ, ω world, x:A @ w ` N : C @ w
unpack
Γ ` e; unpack ω, x = ◦ in N : ∃ω.A ? w
Figure 4.8: Syntax and typing for evaluation contexts. The type A in the judgment
Γ ` e : A ? w is the type that the context expects (the type of the “hole” ◦).
69
e; unpack ω, x = ◦ in N |w 0
7→w e |w [ w /ω ][ v/x ]N
val (pack w0 , v as ∃ω.A)
e |w unpack ω, x = M in N 7→w e; unpack ω, x = ◦ in N |w M
e |w localhost() 7→w e |w val w
e |w #1 M 7 w
→ e; #1 ◦ |w M
e |w #2 M 7→w e; #2 ◦ |w M
e; #1 ◦ |w val h v1 , v2 i 7→w e |w v1
e; #2 ◦ |w val h v1 , v2 i 7→w e |w v2
e |w M N 7→w e; ◦ N |w M
e; ◦ N |w val v 7→w e; v ◦ |w N
e; (λλx.M ) ◦ |w valv 7→w e |w [ v/x ]M
e |w M hwi 7 w
→ e; ◦hwi |w M
e; ◦hwi |w val Λω.v 7→w e |w val[ w/ω ]v
e |w put u = M in N 7→w e; put u = ◦ in N |w M
e; put u = ◦ in N |w val v 7→w e |w [ lift w v/u ]N
e |w get[M ] N 7→w e; get[◦] N |w M
e; get[◦] N |w val w0 7→w e; return[w] ◦ |w0 N
e; return[w] ◦ |w0 val v 7→w e |w val (vval(lift w0 v))
e |w let x = M in N 7→w e; let x = ◦ in N |w M
e; let x = ◦ in N |w val v 7→w e |w [ v/x ]N
e |w leta x = M in N 7→w e; leta x = ◦ in N |w M
e; leta x = ◦ in N |w val held v 7→w e |w [ v/x ]N
e |w lets u = M in N 7→w e; lets u = ◦ in N |w M
e; lets u = ◦ in N |w val sham s 7→w e |w [ s/u ]N
e |w val (vval ω.v) 7→w e |w val [ w/ω ]v
Figure 4.9: The small-step evaluation relation for MinML5 internal language machines.
Figure 4.10: The definition of the partial function lift, which takes a world and a value,
and returns a valid value.
70
to define a special validity substitution operation as in Section 3.5.) The reason that this
nondeterminism is acceptable is that we have in mind an implementation where valid
values and values have the same representation, meaning that this instantiation has no
run-time effect.
The MinML5 internal language is type safe:
Theorem 33 (Type preservation of 7→w )
If D :: · ` e |w M ok
and E :: e |w M 7→w e0 |w0 M 0
then F :: · ` e0 |w0 M 0 ok
Theorem 34 (Progress for 7→w )
If D :: · ` e |w M ok
then either E :: e |w M 7→w e0 |w0 M 0 for some e0 , w0 , and M
or e |w M is terminal
The only terminal state is finish |w val v. Proof of Theorem 33 is an easy induction
on E and appears as the Twelf relation presv in Appendix A.8.3. The only thing to
mention is that we need a lemma that the lift operation produces a well-typed valid
value if its input is well-typed:
Theorem 35 (Type preservation of lift)
If D :: Γ ` v : A @ w
and E :: lift w v = s
then F :: Γ ` s : ω.A
Proof is straightforward by induction on E, and the full proof appears as the Twelf
relation momo in Appendix A.8.3.
Proof of Theorem 34 is also straightforward. The necessary lemma is that lift w v is
defined whenever v has mobile type:
Theorem 36 (Totality of lift on values of mobile type)
If D :: · ` v : A @ w
and E :: A mobile
then F :: lift w v = s (for some s)
It is also an easy induction, appearing as the relation mobmov in Appendix A.8.3.
To conveniently state the progress theorem, we use the standard trick of defining the
evaluation relation in Twelf to relate a terminal machine to itself. The totality of the
relation encodes the disjunctive theorem statement, by either encoding 7→w when one
of its steps applies, or by self-stepping when the machine is terminal. The proof appears
as the Twelf relation presv in Appendix A.8.3. 2
4.4 Elaboration
Having defined the external and internal languages, we can now give the elaboration
relation. The translation is type-directed, so it is only defined for well-typed terms,
and is defined inductively on the external language typing derivation rather than the
71
[[A ∧ B]]E = [[A]]E ∧ [[B]]E
..
.
[[2A]]E = ∀ω.((unit → [[A]]E ) at ω)
[[3A]]E = ∃ω.([[A]]E at ω ∧ ω addr)
Figure 4.11: The elaboration relation from external language types to internal language
types.
term. Since we lack a convenient notation for functions on typing derivations, I will
give the translation informally on the syntax and assume access to some parts of the
typing derivation (types and worlds) that are necessary. The formal relation is defined
in Twelf, and is discussed briefly in Section 4.4.1.
We define a collection of relations, relating external language types, expressions, val-
ues, and valid values to their internal language counterparts. Since most of the external
language constructs have a directly analogous counterpart in the internal language, I
avoid giving those cases except for illustration.
The translation of types is a good starting point, since it shows how 2 and 3 will be
treated. It is given as a function [[·]]E , defined for all well-formed external language types,
and appears in Figure 4.11. Most cases are pointwise, as the case given for ∧. A value of
type 2A is a suspended computation that can be evaluated at any world. Therefore we
translate this type to a function taking the trivial unit argument (a suspension) which
is encapsulated with at to mark that it makes sense at some other world, and then that
world is made polymorphic with ∀. A value of type 3A is a value that makes sense
at some abstract world. We pair that value with a dynamic address for that world, and
then pack it into the existential type to hide the world. (Recall that the external language
constructs for 2 do not do anything with addresses, since the suspended expression can
just compute its address with localhost() if desired.) Because 2A and 3A are mobile
for any A, it is important that their translations be mobile as well. This is the case here
because A at w is also mobile for any A.
@w
Next is the elaboration relation for values, given as a function [[·]]A VE defined for all
well-typed external language values, and indexed by the world and type of the value.
It appears along with the elaboration relation for expressions and valid values (with
which it is mutually recursive) in Figure 4.12. The translation of the box and there
constructors is straightforward knowing the translation of the types. The only thing we
must be careful about is that a translated value is also a value, because we continue to
have syntactic value restrictions in the intermediate language (particularly on held and
Λ). The translation of valid values is given by [[·]]ω.A SE and has only two cases, both of
which are pointwise.
@w
The translation of expressions is given by [[·]]A E and also indexed by the world and
type of the expression. Note how for the expression constructors hM, N i and hold we
just sequence the evaluation of the body and then use the value constructors, obviating
the need for these constructors in the internal language.
For unbox, we apply the translated value (which is polymorphic in its world) to
72
2A @ w @ω
[[boxω.M ]]VE = Λω.(held λx.[[M ]]A E )
3A @ w A@w
[[there[w, v]]]VE = pack w, h held [[v]]VE , wii
as ∃ω.([[A]]E at ω ∧ ω addr)
..
.
..
.
[[u]]ω.A
SE = u
@ω
[[ω.v]]SE = ω.[[v]]A
ω.A
VE
Figure 4.12: The elaboration relation from the external language to internal language,
for values, expressions, and valid values.
73
the current world. This gives us an expression of type (unit ⊃ A) at w, which we
consume by using leta. Note that because this term is “at” the current world, when
we bind it with leta we get a local hypothesis which we can use immediately. We then
apply that function to h i , which evaluates the suspension. The translation of here is
like there, but we sequence the evaluation of the expression and use localhost() to
compute the current world’s address before packing the existential. The elimination
form, letd, simply unpacks the term, then projects out the stored address and value.
Observe that in this case our inductive call to [[M ]]3A
E
@w
must apparently “guess” the
type A. This is why (for example) the translation is actually defined on type derivations;
in the derivation that the letd expression is well-typed, we can simply read off the type
3A from the subderivation that M is well-typed.
Elaboration is type-correct and defined for all well-formed types, values, expres-
sions, and valid values:
Theorem 37 (Functionality of type elaboration)
[[A]]E is defined and unique for all well-formed A
Theorem 38 (Static correctness of expression elaboration)
If D :: Γ ` M : A @ w (in the external language)
0
A@w
then [[v]]VE = M (for some M 0 )
and [[Γ]] ` M 0 : [[A]]E @ w (in the internal language)
Theorem 39 (Static correctness of value elaboration)
If D :: Γ ` v : A @ w (in the external language)
0
A@w
then [[v]]VE = v (for some v 0 )
and [[Γ]] ` v 0 : [[A]]E @ w (in the internal language)
Theorem 40 (Static correctness of valid value elaboration)
If D :: Γ ` s ∼ ω.A (in the external language)
0
then [[s]]ω.A
SE = s (for some s0 )
and [[Γ]] ` s0 ∼ ω.[[A]]E (in the internal language)
The operation [[Γ]] simply translates the types of the variables in Γ in the obvious
way. The informal proof of each theorem is by induction on the typing derivation (or
for Theorem 37, the structure of the type). The formal proof comes from the totality
of the Twelf translation relations on typing derivations, which is discussed in the next
section.
74
lessons will be important for understanding the Twelf encodings of CPS (Section 4.6.2)
and closure conversion (Section 4.7.1).
To refresh the reader’s memory, I give a brief and informal tour of Twelf and how it can
be used to formalize programming languages and their metatheory. For a more com-
plete and formal discussion, I recommend Harper and Licata’s Mechanizing Metatheory
in a Logical Framework [52].
Twelf is an implementation of the LF logical framework [51], which allows us to
encode judgments as type families in a dependently-typed lambda calculus. We can en-
code a programming language’s syntax as a type family, and then encode the language’s
typing judgment in terms of its syntax [105]. For example, a fragment of the MinML5
internal language is as follows:
world : type.
ty : type.
val : type.
unit : ty.
at : ty -> world -> ty.
1 : val.
held : val -> val.
We define three type families, for the syntactic classes of worlds, types, and values
(type is a Twelf keyword, whereas ty is the name of the syntactic class in MinML5).
We then declare that the constant unit is a ty, and that the constant at, applied to a
type and a world, is a ty. Finally, we declare the value 1 and value constructor held.
Having made these definitions, we can then define the typing judgment using the same
mechanisms:
ofv : val -> ty -> world -> type.
75
indicating the world. The atI constructor is similar, but takes a subderivation that the
embedded value is well-typed.
Because Twelf is also a logic programming system, we can define relations on terms
that have computational content. For example, supposing we have declarations for
external language values, well-typedness (suffixed with the letter e; for example, vale,
and ofve). We can then define an elaboration relation for values:
elabv : vale -> val -> type.
%mode elabv +VE -VI.
elabv/1 : elabv 1e 1.
elabv/held : elabv (helde V) (held V’)
<- elabv V V’.
This type family relates an external language value with an internal language value.
By declaring its mode, we assert that the relation can be run as a program, where the
external language value is an input (+) and the internal language value is an output
(-). Running this relation as a program means requesting a derivation of elabv v VI
where v is a canonical LF term of type vale and VI is an existential variable. Twelf’s
operational semantics defines how to search the constants defining elabv to find a
matching derivation. In the case that the input is (helde 1e), for example, search
sees that only elabv/held could be the outermost constructor on the derivation. Since
this constructor needs an argument, it recursively searches for a derivation of elabv 1
V’, finds that elabv/1 matches, and so returns the derivation elabv/held elabv/1.
Metatheorems
To prove a metatheorem about a relation, we use Twelf’s facilities for verifying totality
assertions [52, 121]. For instance, we typically want to know that a translation is type
preserving. We can state this for elabv as an LF relation:
elabok : ofve V A W ->
elabv V V’ ->
ofv V’ A W ->
type.
%mode elabok +WV +EL -WV’.
The idea is that elabok relates three derivations: the derivation that V is well formed,
the derivation that V translates to V’, and a derivation that V’ is well-formed at the same
type and world. What we want to express is that if there are derivations for first two
positions (the inputs) then there is a derivation for the output position. We use the rela-
tion’s mode to indicate the inputs and outputs, as above. The proof of this theorem has
two parts. We first give constants defining this relation, such that Twelf’s search strategy
will always find a derivation of elabok it when a query is made with canonical input
derivations. We then use Twelf’s metatheory system to verify that our proof (a program
that transforms the input derivations into the output derivation) always succeeds. This
property is called totality.
76
The cases for this proof (constants inhabiting elabok) are:
elabok/1 : elabok unitIe elabv/1 unitI.
elabok/held : elabok (atIe WV) (elabv/held E) (atI WV’)
<- elabok WV E WV’.
To verify that this relation is total, we first specify what sorts of LF terms may appear
in the context:
%block w : block {W : world}.
%worlds (w) (elabok WV EL WV’).
In Twelf terminology, the “world” is a description of the context. This is somewhat
confusing in the current work, where we use “world” in a completely different way. I
therefore say “context” except when using the %worlds keyword.
In this case we assume only hypotheses of the existence of object language worlds
(without this, there would be no derivations of ofv at all, since we declared no constants
of type world). The %worlds declaration checks that the relation recursively maintains
this form of context. We can then assert the totality of the relation elabok.
%total EL (elabok WV EL WV’).
The parameter EL is the induction metric, which in this case is just the structure of
the second input. Internally, Twelf checks several properties when verifying a totality
assertion. First, it checks that for any query matching the mode, some case of the proof
matches it. It then checks that any subgoals (premises of the case) obey the induction
metric, so that proof search always makes progress. (There are also some subtle restric-
tions on the form of subgoals, which I do not discuss here.) If the totality assertion
succeeds, then we know formally that for all canonical LF terms in the input positions
(living in the context we declared), there exists a canonical LF term in the output posi-
tion. If we believe that the LF terms adequately represent what we intend, then we have
a proof of an “If. . . then” statement. In this case, we have proved that if Γ ` v : A @ w and
[[v]] = v 0 then Γ ` v 0 : A @ w, provided that Γ takes the form ωi world . . . ωn world.
77
delabv/1 : delabv unitIe unitI.
delabv/held : delabv (atIe WV) (atI WV’)
<- delabv WV WV’.
78
Language Declaration Meaning
EL
world : type Worlds
IL
EL tye : type
Types
IL ty : type
EL vale : type
Values
IL val : type
EL expe : type
Expressions
IL exp : type
EL vvale : type
Valid values
IL vval : type
EL ofe : expe -> tye -> world -> type
Typing of exps
IL of : exp -> ty -> world -> type
EL ofve : vale -> tye -> world -> type
Typing of values
IL ofv : val -> ty -> world -> type
EL ofvve : vvale -> (world -> tye) -> type Typing of
IL ofvv : vval -> (world -> ty) -> type valid values
Figure 4.13: Twelf type families defining the external language and internal language.
79
ettoit_gimme : {A:tye} {A’:ty} ettoit A A’ -> type.
%mode ettoit_gimme +A -A’ -D.
This is known as an “effectiveness” lemma [68] and is also easy to prove.
A failed attempt
To see why we must have both an input and output version of our theorems, let us try
to prove elab+ and elabv+ directly. We will consider the cases for application and
lambdas. Application does work:
- : elab+ _ _ (=>Ee Df Da) ETb _ (=>E Df’ Da’)
<- ettoit_gimme A A’ ETa
<- elab+ _ _ Df (ettoit_=> ETa ETb) _ Df’
<- elab+ _ _ Da ETa _ Da’.
The application of a function of type A => B has type B, so we get an input deriva-
tion ETb : ettoit B B’. To elaborate the function and argument, we also need a
derivation of ETa : ettoit A A’. An appeal to the functionality lemma gives us this,
and then we can apply induction on both components.
The lambda case comes close to working, but fails because of the way variables are
handled in Twelf. A (very) naı̈ve attempt is as follows
- : elabv+ _ _ (=>Ie D) (ettoit_=> ETa ETb) _ (=>I D’)
<- ({ve : vale}{ove : ofve ve A W}
{v : val} {ov : ofv v A’ W}
elab+ _ _ (D ve ove) ETb _ (D’ v ov)).
Since we are translating a lambda, we know that the external language type is of
the form A => B, and therefore its translation takes the form ettoit_=> ETa ETb
(ettoit_=> is the case of the translation for arrow types, and takes derivations of the
translations of A and B). This gives us a translation derivation for B which we can pass
off to translate the body. However, in doing so we have introduced EL variables into the
context, which means that we need to say how to elaborate them. This is accomplished
by adding an elaboration derivation into the context along with the variable and then
declaring (with the %worlds declaration) that EL variables are always accompanied by
elaboration derivations. The standard thing to try here would be:
- : elabv+ _ _ (=>Ie D) (ettoit_=> ETa ETb) _ (=>I D’)
<- ({ve : vale}{ove : ofve ve A W}
{v : val} {ov : ofv v A’ W}
{thm : elabv+ A ve ove ETa v ov}
elab+ _ _ (D ve ove) ETb _ (D’ v ov)).
%block blockve :
some {A:tye}{A’:ty}{W:world}{ETv : ettoit A A’}
block {ve : vale}{ove : ofve ve A W}
{v : val} {ov : ofv v A’ W}
{thm : elabv+ A ve ove ETv v ov}.
80
%worlds (blockve) (elab+ _ _ _ _ _ _) (elabv+ _ _ _ _ _ _).
This does not work. The reason is somewhat subtle. To check that our proof covers
all of the cases, Twelf looks at the constants defining our language and the description
of the context given by the %worlds declaration to see what possible forms the input
might take. It then checks to see that the input is matched either by one of the cases
of the proof, or by a case that must be in the context because of its regular form. For
elabv+, the inputs are the type, value typing derivation, and type translation. This
includes variables from the block declaration we just made, so we must be able to match
elabv+ A ve ove ET _ _
for some arbitrary A : tye, A’ : tye and ET : ettoit A A’. None of the cases will
match variables because they all case-analyze the form of the derivation. Therefore,
only the case that we inserted for the variables could match. Unfortunately, it doesn’t.
The derivation ET of ettoit A A’ that we’re trying to match isn’t necessarily the same
as the one that we put into the context with the variables; in fact, it doesn’t necessarily
even translate A to the same A’. Input coverage therefore fails. Although the reasoning
to show that this doesn’t matter (because ettoit is a function) is easy to carry out, we
have no place to do it, so this attempt is a dead end.
%block blockve :
some {A:tye}{A’:ty}{W:world}{ETv : ettoit A A’}
block {ve : vale}{ove : ofve ve A W}
{v : val} {ov : ofv v A’ W}
{thm : elabv- A ve ove ETv v ov}.
Because we must output the type translation, we sometimes must appeal to effec-
tiveness, as we do here. When we descend under the binder, we introduce external lan-
guage and internal language variables, and the case for elaboration that relates them.
Since the type translation derivation is now an output, it is not considered during input
coverage checking and therefore does not suffer from the problem above.
In essence, we have changed the meaning of the theorem from “give me any type
translation you like and I will elaborate using it” to “I will elaborate and tell you which
81
type translation I used.” Since there is only one translation of any type, these two are
actually the same, but it is easier to check coverage for the second. We carry out the
equality reasoning to implement the elab+ relations in terms of the elab- ones. This
is a single case that covers all inputs:
of_resp : of M A W -> eqtyp A A’ -> of M A’ W -> type.
%mode of_resp +BOF +EQ -BOF’.
Theorem
The rest of the cases go through easily using these same techniques. (The elaboration
of valid values only needs a “-” version because is syntax is so simple.) Once they are
complete, we can verify the totality all of the (mutually inductive) elaboration relations
at once:
%total (D1 D2 D3 D4 D5) (elab- _ _ D1 _ _ _)
(elab+ _ _ D2 _ _ _) (elabv- _ _ D3 _ _ _)
(elabv+ _ _ D4 _ _ _) (elabvv _ _ D5 _ _ _).
Induction is on the typing derivation, as expected. The order of the relations here
is important, because the one case of elab+ appeals to elab- on a typing derivation
82
of the same size; therefore we must consider the elab- relation to be smaller in our
simultaneous induction metric. The full proof appears in Appendix A.8.5. 2
Elaboration is a simple translation, but its formalization displays some of the tech-
niques that are important for the following translations, which are less straightforward.
The first is conversion to continuation passing style, which begins in the next section.
Γ ` c?w
states that the continuation c is well-formed for evaluation at w. These are mostly
straightforward. To call a continuation (Rule call) we simply need an argument value
83
conts c ::= leta x = v in c
| lets u = v in c
| put u = v in c
| let x = fst v in c
| let x = snd v in c
| let x = localhost() in c
| let x = vhwi in c
| let ω, x = unpack v in c
| go[w; va ] c
| call vf (vx )
| halt
values v ::= h v1 , v2 i | h i | held v | pack w, v as ∃ω.A
| λ x.c | Λω.v | vval v | w | sham s | x
valid values s ::= u | ω.v
types A, B ::= A cont | A ∧ B | A at w | ω A
| ∀ω.A | ∃ω.A | w addr | unit
ωM addr M at M
ωA mobile w addr mobile A at w mobile
unit M
unit mobile
Figure 4.15: The definition of the mobile judgment for the CPS language.
84
hyp Γ ` s ∼ ω.A
vval
Γ, x:A @ w ` x : A @ w Γ ` vval s : [ w/ω ]A @ w
Γ ` v1 : A @ w Γ ` v2 : B @ w Γ, x:A @ w ` c ? w
∧I ⊃ I
Γ ` h v1 , v2 i : A ∧ B @ w Γ ` λ x.c : A cont @ w
Γ ` v : A @ w0 Γ, ω world ` v : A @ w
at I ∀I
Γ ` held v : A at w0 @ w Γ ` Λω.v : ∀ω.A @ w
Γ ` v : [ w/ω ]A @ w
address ∃I
Γ ` w : w addr @ w0 Γ ` pack w, v as ∃ω.A : ∃ω.A @ w
Γ, ` s ∼ ω.A
unit I I
Γ ` h i : unit @ w Γ ` sham ω.s : ω A @ w
Γ, ω world ` v : A @ ω
vhyp valid
Γ, u∼ω.A ` u ∼ ω.A Γ ` ω.v ∼ ω.A
Figure 4.16: The CPS language typing rules for values and valid values.
of the right type; both must be at the current world. The continuation halt is well-
typed at any world. Like get, the go construct requires the address of the target world
(Rule go), and the subcontinuation must be well-typed there. However, it does not re-
quire its argument to be mobile—in fact, it has no type to insist is mobile in the first
place! The go construct is only concerned with control flow, as will be clear from the
dynamic semantics in the next section. The translation from get to go will encode the
data transfer using put, which still requires its argument to be mobile. This translation
is described in Section 4.6, immediately following the dynamic semantics.
c 7→w c0
It is indexed by the concrete world at which evaluation is taking place, as usual. Because
a value may be an uninstantiated valid value, we also need an evaluation relation for
values.
v w v0
This relation ensures that v 0 is not of the form vval s, by instantiating s at the current
world if v is of that form. Because we consider such instantiations to have no runtime
85
Γ ` v : A at w0 @ w Γ, x:A @ w0 ` c ? w
leta
Γ ` leta x = v in c ? w
Γ ` v : ω A @ w Γ, u∼ω.A ` c ? w
lets
Γ ` lets u = v in c ? w
Γ ` v : A @ w A mobile Γ, u∼A ` c ? w
put
Γ ` put u = v in c ? w
Γ ` v : A ∧ B @ w Γ, x:A @ w ` c ? w
fst
Γ ` let x = fst v in c ? w
Γ ` v : A ∧ B @ w Γ, x:B @ w ` c ? w
snd
Γ ` let x = snd v in c ? w
Γ, x:w addr @ w ` c ? w
lh
Γ ` let x = localhost() in c ? w
0
Γ ` v : ∀ω.A @ w Γ, x:[ w /ω ]A @ w ` c ? w
wapp
Γ ` let x = vhw0 i in c ? w
Γ ` va : w0 addr @ w Γ ` c ? w0 go
Γ ` go[w0 ; va ] c ? w
Γ ` vf : A cont @ w Γ ` va : A @ w
call
Γ ` call vf (vx ) ? w
halt
Γ ` halt ? w
Figure 4.17: The CPS language typing rules for continuation expressions.
86
vval ω.v w [ w/ω ]v
v w v iff v 6= vval ω.v 0
Figure 4.18: The value instantiation relation for the CPS language. The world index on
the relation is used when instantiating a valid value; however, this use has no run-time
effect. I use a side condition to cover the cases for all value forms that are not vval, since
these are the same.
significance, these steps are just coercions. The relation is defined in Figure 4.18.
The evaluation relation for continuation expressions appears in Figure 4.19. It uses a
partial function lift (Rule put), which is defined identically to the one from the internal
language in Figure 4.10 (so I do not repeat it here). Recall that lift hoists a value to a valid
value, and that it is not defined for all inputs.
The evaluation relation also uses the value stepping relation we just defined. For
elimination rules, we expect the input value to have a particular form; we achieve this
form by appealing to the value stepping relation. For go, we simply check that the
address matches the indicated world, and then start evaluating the nested continuation
(the next step will therefore take place at the remote world).
87
vf w λ x.c
call
call vf (va ) 7→w [ va/x ]c
lift wv = v 0 put
0
put u = v in c 7→w [ ω.v /u ]c
v w held v 0
0 leta
leta x = v in c 7→w [ v /x ]c
v w sham s
lets
lets u = v in c 7→w [ s/u ]c
v w h v1 , v2 i
fst
let x = fst v in c 7→w [ v1/x ]c
v w h v1 , v2 i
snd
let x = snd v in c 7→w [ v2/x ]c
lh
let x = localhost() in c 7→w [ w/x ]c
v w Λω.v 0 wapp
0
0 [ w /ω ]v 0
let x = vhw i in c 7→w [ /x ]c
v w pack w0 , v 0 as ∃ω.A
0 0 unpack
let ω, x = unpack v in c 7→w [ w /ω ][ v /x ]c
va w w 0 go
go[w0 ; va ] c 7→w c
88
[[A ⊃ B]]C = ([[A]]C ∧ [[B]]C cont) cont
[[A at w]]C = [[A]]C at w
[[∀ω.A]]C = ∀ω.[[A]]C
[[∃ω.A]]C = ∃ω.[[A]]C
[[unit]]C = unit
[[A ∧ B]]C = [[A]]C ∧ [[B]]C
[[ ω A]]C = ω [[A]]C
[[w addr]]C = w addr
89
In CPS form, we must first evaluate M to a value and then apply the let...fst
construct to it. So, we begin by recursively calling convert on M with a new meta-level
continuation K0 . Here, the syntax “where” is an in-place mathematical definition; it
is not part of the CPS language. K0 is defined to take the value, project out the first
component and bind it to a variable x. We then pass this variable (a value) to the initial
continuation K. Observe that we have to guess the type B in the recursive use of convert.
Again, this description of the translation is informal; the real conversion is defined over
typing derivations, where the type B can be gleaned from the derivation that M is well-
typed.
The full translation for expressions is given in Figure 4.21, and most of the cases fol-
low this same form. The case for function application is interesting; after evaluating M
and N to values, M is a continuation that expects an argument and a return continua-
tion. We therefore pair the value resulting from N with a reification of the meta-level
continuation as an object-level λ . For an internal language function value (Figure 4.22)
we project the original argument from the argument tuple and then run the body; when
it finishes, we project the continuation argument and call it on the result. The translation
of other values is completely pointwise. Finally, the internal language get construct de-
composes into two uses of go. First we save the current world’s address, and make it
valid with put so that we can use it from the remote world. We then go to the remote
world using the supplied address and evaluate the body. The resulting value must be
of mobile type, so we put it to make it valid as well. Using the saved address, we go
back and continue with the valid value. In this way we have separated the control and
data mobility aspects of get into the go and put constructs completely; now only go
changes between worlds and only put uses the mobile judgment.
To convert a whole program we need an initial continuation to pass to convert. The
natural choice is one that ignores its argument and returns the halt continuation ex-
pression.
90
convert K (val v) A w = K(v)
convert K (#1 M) A w = convert K’ M (A ∧ B) w
where K’(v) = let x = fst v in K(x)
convert K (#2 M) A w = convert K’ M (A ∧ B) w
where K’(v) = let x = snd v in K(x)
convert K (let x = M in N) C w = convert K’ M A w
where K’(v) = [ v/x ](convert K N C w)
convert K (unpack ω, x = M in N) C w =
convert K’ M (∃ω.A) w
where K’(v) = let ω,x = unpack v in convert K N C w
convert K (leta x = M in N) C w =
convert K’ M (A at w0 ) w
where K’(v) = leta x = v in convert K N C w
convert K (lets x = M in N) C w =
convert K’ M ( ω A) w
where K’(v) = lets u = v in convert K N C w
convert K (put u = M in N) C w =
convert K’ M A w
where K’(v) = put u = v in convert K N C w
convert K (localhost()) (w addr) w = let x = localhost() in K(x)
0
convert K (M hw0 i) ([ w /ω ]A) w = convert K’ M A w
where K’(v) = let x = vhw0 i in K(x)
convert K (M N ) B w = convert K’ M (A ⊃ B) w
where K’(v) = convert K” N A w
where K”(v’) = call v(h v’, λx.K(x)i)
convert K (get[M] N) A w = convert K’ M (w0 addr) w
where K’(va ) = let xr = localhost() in
put ur = xr in
go[w’; va ] convert K” N A w’
where K”(v) = put u = v in
go[w; vval ur ] K(vval u)
Figure 4.21: The translation of internal language expressions to CPS expressions. The
function convert takes a meta-level continuation from CPS values to CPS expressions, an
internal language expression, its type and world. It returns a CPS expression. Variables
that do not appear in the input are assumed to be completely fresh.
91
@ A∧B @A @B
[[hhv1 , v2 i ]]w
VC = h [[v1 ]]w
VC , [[v2 ]]w
VC i
..
.
@ A⊃B
[[λλx.M ]]w
VC = λ [Link] x = #1 y in
convert K M B w
where K(v) = let xk = #2 y in xk v
Figure 4.22: The translation of internal language values to CPS values. Only the case
for functions is interesting, so I give that along with the straightforward example of pair
values.
92
Declaration Meaning
ctyp : type CPS types
cval : type CPS values
cexp : type Continuation expressions
cvval : type CPS valid values
cof : cexp -> world -> type Well-formedness of continuation
expressions
cofv : cval -> ctyp -> world -> type Well-formedness of values
cofvv : cvval -> Well-formedness of
(world -> ctyp) -> type valid values
ttoct : typ -> ctyp -> type Translation of IL types to CPS
types
ttoctf : (world -> typ) -> Translation of types
(world -> ctyp) -> type parameterized by world
93
% well-formedness of output is
% parameterized by well-behavedness
% of K
{WCC :
{K : cval -> cexp}
({cv : cval}
{wcv : cofv cv CA W}
cof (K cv) W) ->
cof (CC K) W}
type.
%mode tocps- +M +WM -CT -CC -WCC.
%mode tocps+ +M +WM +CT -CC -WCC.
The first three arguments are straightforward: the IL expression to convert, the
derivation of its well-formedness, and the translation of its IL type to a CPS type. (This
third argument is either an output or an input depending on whether this is tocps- or
tocps+.) The two outputs are higher-order, like the convert function and its statement
of type correctness from the previous section. The output CC is a continuation expres-
sion that is parameterized by what we called K; here it is an LF function that produces
a continuation expression from the value representing the result of evaluating M . For
example, if we CPS-convert the IL expression localhost, CC will be the LF term
(Recall that in the CPS language, localhost takes the form let x = localhost() in c.
The LF constant clocalhost has type (cval → cexp) → cexp, using higher-order
abstract syntax in the standard way to encode the binding of x within c.) If we apply CC
to the standard initial K, λy : cval. halt, we get
as expected.
CC, the output of the translation, must be also be well-formed. Since it is parame-
terized by K, the well-formedness of CC is contingent upon the well-behavedness of K.
Therefore the output WCC, representing the well-formedness of CC, has several nested
implications. First, we quantify over all K. Then, assuming some value cv and deriva-
tion that is well-formed, wcv, K applied to that value must be well-formed. If that is
true, then CC applied to K will also be well-formed.
The type of this relation may be more clear when we see how it is used. But before
we are able to give the translation for expressions and values, we need to prove some
lemmas about the type translation relation.
Lemmas
First, we define a shallow (non-inductive) equality relation on continuation types,
which internalizes LF equality:
94
ceqtyp : ctyp -> ctyp -> type.
ceqtyp_ : ceqtyp A A.
We can then prove functionality and effectiveness for the type translation relation
ttoct. Functionality means that the output is deterministic, and effectiveness that the
translation can be performed for any input.
ttoct_fun : ttoct A A’ -> ttoct A A’’ -> ceqtyp A’ A’’ -> type.
%mode ttoct_fun +X +Y -Z.
ttoct_gimme : {A:typ} {A’:ctyp} ttoct A A’ -> type.
%mode ttoct_gimme +A -A’ -D.
(We have similar lemmas for the ttoctf relation, not shown here.) We need to
prove that continuation value typing respects equality, and that partial continuation
typing (the WCC output of tocps- above) respects equality. These theorems are trivial
because equality is shallow.
cofv_resp : cofv C A W -> ceqtyp A A’ -> cofv C A’ W -> type.
%mode cofv_resp +COF +EQ -COF’.
wcc_resp : {WCC :
({K : cval -> cexp}
({cv : cval}
{wcv : cofv cv A W}
cof (K cv) W) ->
cof (CC K) W)}
{WCC’ :
({K : cval -> cexp}
({cv : cval}
{wcv : cofv cv A’ W}
cof (C cv) W) ->
cof (CC K) W)}
type.
%mode wcc_resp +K +EQ -K’.
The proofs of these lemmas are uninteresting and appear in Appendices A.8.4
and A.8.6.
Translation
We can now give the translation for expressions. We start by proving the tocps+ ver-
sion, since it is only one case:
tocps+/- : tocps+ V WV (CTi : ttoct A A’) CC WCC
<- tocps- V WV (CTo : ttoct A A’’) CC WCC’
95
<- ttoct_fun CTo CTi (EQ : ceqtyp A’ A’’)
<- wcc_resp WCC’ EQ WCC.
This looks almost the same as the analogous case from elaboration. We receive a
derivation of the type translation CTi as input, and appeal to the tocps- version im-
mediately, which gives us another type translation CTo. We then use functionality of
ttoct to see that CTo and CTi give the same result, and use the fact that WCC respects
equality of types to get a WCC’ that mentions A’ instead of A’’, as required.
The translation of the IL projection fst is illustrative.
c_fst : tocps- (fst M) (&E1 WM) CT
% parameterized expression resulting from translation
([k:cval -> cexp]
CC ([v:cval] cfst v ([a:cval] k a)))
% its parameterized typing derivation
([k : cval -> cexp][wk : ({cv : cval}
{wcv : cofv cv CA W}
cof (k cv) W)]
WCC _ ([v][wv] co_fst wv wk))
<- tocps- M WM (ttoct/& CT _) CC WCC.
To translate the expression fst M, we inductively translate the argument. It must
have type A & B and so the only case for translating it is ttoct/&, so this subgoal
covers all outputs. It returns the translation for A, called CT, and the translation of
M and its well-formedness, called CC and WCC respectively. Our job is now to build the
continuation expression for the fst projection and its typing derivation. The expression
is parameterized by k, which takes the result of the fst operation. Its body works by
first calling CC (the translation of M) and supplying it with a continuation that binds the
result of M to the variable v. We then project the first component using cfst, and apply
the outer continuation k to the result.
The parameterized typing derivation follows the same plan. It takes a continua-
tion k and a typing derivation for it, wk. The derivation starts with the typing for the
translation of M, which is represented by the function WCC. We apply WCC to the actual
continuation we supply above—Twelf can deduce what this is by unification, so we
just write to avoid repeating ourselves. The second argument is the typing derivation
for that continuation; it takes a variable v representing the result of evaluating M and a
typing derivation for it wv. The derivation consists simply of the typing rule for c fst
applied to the well-formedness of its argument and the code that follows, both of which
we get from arguments.
The translation is challenging because it is so high-order (typing responsibilities pass
from callee to caller and vice versa) but most of the cases follow the same pattern.
Twelf’s term reconstruction allows us to supply only the essence of the translation and
it can often determine the rest. In particular, because typing derivations are indexed by
the terms that they type, we can usually perform the translation on typing derivations
and this will induce the appropriate translation for terms automatically. For example,
we can write the case for application as follows:
96
c_app : tocps- (app M N) (=>E WM WN) CTB _
([c][wc]
% eval function, then argument
FM _ ([f][wf]
FN _ ([a][wa]
co_call wf
(cov_pair wa (cov_lam ([r][wr] wc r wr)) ))))
<- ttoct_gimme (A => B) (A’ c& (B’ ccont) ccont) (ttoct/=> CTA CTB)
<- tocps+ M WM (ttoct/=> CTA CTB) _ FM
<- tocps+ N WN CTA _ FN.
The CC output is just , and is recovered by Twelf from the WCC output ([c][wc]
. . . ). The first subgoal in this case (ttoct gimme) exists to reconcile the various type
translations that will occur: We will have a translation for A, B, and A => B which all
must agree. We therefore invoke the effectiveness lemma on the largest type (A =>
B) and the others will be subterms. We then invoke tocps inductively to translate
the function and argument expressions (we use the “+” version here so that we need
not do equality reasoning about the type translations). The resulting typing derivation
begins with the derivations for the translation of M and N. Given these, it builds a pair
of the argument wa and return continuation. The body of the return continuation is the
outermost continuation passed to the translation of the app. We then end with a call to
the translated function value on the pair we created.
Binding
Constructs with binders require us to embed the case for variables within the subgoal.
For example, the translation of the IL let construct is as follows:
c_let : tocps- (let M N) (oflet WM WN) CTN _
([c][wc] FM _ ([v][wv] FN v wv c wc))
<- ttoct_gimme A A’ CTM
<- tocps+ M WM CTM _ FM
<- ( {x}{xof : ofv x A}
{x’}{x’of : cofv x’ A’}
{thm:tocpsv- xof CTM x’of}
tocps- (N x) (WN x xof) CTN (CC x’) (FN x’ x’of)).
This translation begins as before, by translating M. We then want to translate the
body, N, but it has type val -> exp, so it must be in a subgoal with a hypothetical
value variable in context. The subgoal actually introduces five hypotheses: the direct
style value x; a derivation that it is well-formed at type A, xof; the CPS value it will
be translated to, x’; its derivation x’of; and the case of the theorem that relates the
two. Once we have translated N and WN in this context, we build the result typing
derivation. Because we have set up the translation such that the continuation always
takes a value as an argument, we do not need a CPS-level let construct; we simply
invoke FM, which types the translation of M, and then invoke FN on the value and typing
derivation resulting from that, and finish with the outermost continuation.
97
The other cases follow these same patterns or techniques already used in elaboration.
They can be found in full in Appendix A.8.6. In this formalism, the type-correctness
of the translations is manifest in the fact that they translate typing derivations. The
termination of the translations and their definedness for all well-typed inputs comes
from the Twelf totality assertions. 2
To generate an environment for this function, we need to reify the remote hypothesis
as a value. Because it does not make sense here, we do this by using the at modality.
Similarly, valid hypotheses are encapsulated using the ω modality.
A continuation of type A cont could have any set of free variables, and therefore can
have an arbitrary environment type. It is important, however, that the translation of the
CPS type A cont to a CC type is only a function of A. We achieve this by hiding the
environment type using an existential type [78, 83]. The translation of the type A cont
is thus
[[A cont]]CC = ∃αenv .αenv × (([[A]]CC × αenv ) cont)
98
types A ::= ... | α
values v ::= ...
| pack B, v as ∃α.A
valid vals s ::= ...
conts c ::= ...
| go[w; va ] c
| go cc[w; va ] vc
| let α, x = unpack v in c
·, x : A @ w ` c ? w
λ
Γ ` λ x.c : A cont @ w
Γ ` v : [ B/α ]A @ w
pack α
Γ ` pack B, v as ∃α.A : ∃α.A @ w
Figure 4.24: The closure converted (CC) language, defined as a modification of the CPS
language in Figures 4.17, 4.16, 4.15, and 4.14.
99
No other types are affected by closure conversion, so the translation [[·]]CC is pointwise
there.
The translation of the program syntax is as follows. We only translate λ values, call
and go expressions; every other construct is translated in a pointwise fashion. The call
construct is a good starting point because it shows how closures are used:
We start by unpacking the closure, to get the environment type and the pair value.
Its first component is the environment and the second is the function; we project these
out. We then call the function with a pair of arguments: the translated argument and
the extracted environment.
The go construct is translated by handing the work off to the case for λ . It is as
follows:
100
The translation of continuations is the crux of closure conversion:1
[[λλx.c]]CC = pack hBenv , hvenv , λ p.c0 ii
as ∃αenv .αenv ∧ (([[A]]CC ∧ αenv ) cont)
where. . .
c0 = let x = #2 p in
let e = #1 p in
leta FV1 = π1 e in
..
.
leta FVn = πn e in
lets FSV1 = πn+1 e in
..
.
lets FSVm = πn+m e in
[[c]]CC
venv = hheld FV1 , . . . , held FVn ,
sham ω.FSV1 , . . . , sham ω.FSVm i
Benv = [[FVT1 ]]CC at FVW1 ∧ . . . ∧ [[FVTn ]]CC at FVWn ∧
ω FSVT1 ∧ . . . ∧ ω FSVTm
The continuation body c0 begins by projecting from its pair argument the real argu-
ment x and the environment e. It then extracts each of the free variables in sequence;
first the free modal variables (x:A @ w0 ) and then the free valid variables (u ∼ ω.A). The
modal variables are encapsulated by the at modality so we use leta to bind them; the
valid variables by and so we use lets to bind those. The type of the environment
reflects this representation; it is an iterated conjunction of Ai at wi and then ωAj .
An interesting observation is how the process of closure conversion exercises the
expressiveness of the programming language. In order to do it, we must be able to
encapsulate any kind of dynamic hypothesis into a value in order to store it in the en-
vironment, and then restore that hypothesis into the context within the body of the
closure. This is a kind of completeness criterion akin to the identity property for se-
quent calculi [106]. At the source level it means that any piece of code can be hoisted
out of its context by abstracting over its free variables, an activity that is common when
1
To simplify the presentation we assume derived forms for iterated products, where h v1 , v2 , . . . , vn i is
h v1 , h v2 , h . . . , vn i i i , and πi is a projection of the ith component.
101
programming. In fact, during the development of this work, we originally omitted the
at connective and used a less precise version of the connective. Our inability to do
closure conversion was what alerted us to their expressiveness (and necessity!).
The typing condition on λ in the CC language ensures that the output is indeed
closure-converted. Each of these theorems is an easy induction. For go, we appeal im-
mediately to induction on λ y.c; this is well-founded because we consider the induction
metric to be lexicographic in the number of occurrences of go and then the size of the
term. For call, after applying induction it is a simple matter of observing that the
unpack, projections, and call are well-typed.
In the case for λ x.c, we have [[Γ]], x:[[A]]CC @ w ` [[c]]CC ? w by induction hypothesis. We
then strengthen this to FV, FSV, x:[[A]]CC @ w ` [[c]]CC ? w where FV and FSV are the sets of
actually occurring modal and valid variables. In the translation, we discharge each of
these hypotheses by the series of lets and leta bindings wrapping the translated c,
leaving us with only the hypothesis p:[[A]]CC ∧Benv representing the argument. This meets
the typing condition for closed lambdas. It is easy to see that venv : Benv and therefore that
the pack is well-formed. 2
As usual, the formal statement and proof of the type correctness of closure conver-
sion is carried out in Twelf. The reader should note that this proof is the least similar
to the hypothetical paper version, because we encode an essentially different closure
conversion algorithm and introduce a special syntactic form for the representation of
closures. This is to keep the complexity of the proof manageable while still getting at its
essence; it is nonetheless the largest Twelf proof in this project. I describe the technique
in the next section.
102
4.7.1 Closure conversion in Twelf
Higher order abstract syntax
Most Twelf encodings use the technique of higher order abstract syntax (HOAS) [108] to
represent binders in an object language using the binding structure of LF. This technique
has many advantages, such as often getting object language mechanisms and metathe-
ory related to binding (such as substitution and its well-behavedness) “for free.”
Sometimes the object language’s notion of binding or substitution does not coincide
with LF’s. For example, in Section 3.4 we had a higher-order substitution operation for
falsehood variables that had to be described explicitly and for which we had to prove a
substitution theorem. This is seldom any trouble because substitution theorems are gen-
erally easy to prove. It can also be the case that we place additional restrictions on the
occurrence of variables. For example, to express linear logic [46] we can use LF’s bind-
ing structure to encode variables but also impose an additional restriction (enforced via
a judgment) that the appearance of these variables follow the rules of linear logic [24].
We define a relation
linear : (val -> exp) -> type.
that takes an expression x.e with one free variable and insists that the variable is used
linearly within that expression. In the well-formedness judgment for terms, we require
for each linear variable bound that it occurs linearly within the body. For example, the
case for functions might be
oflam : ofv (lam ([x:val] M x)) (A -o B)
<- ({x:val} ofv x A ->
of (M x) B)
<- linear M.
We then need to prove that operations like substitution preserve linearity, but these are
straightforward properties.
We can sometimes encounter trouble with HOAS encodings because of the struc-
tured way in which we interact with the context in Twelf. For these, we can always
resort to a first-order representation where contexts are represented explicitly, but then
it becomes very tedious to manipulate terms and prove metatheory—at this level of
detail, many things that we take for granted (such as the commutation of bindings,
weakening, equality reasoning, etc.) become a substantial fraction of the work. (Fortu-
nately, it is possible to convert to explicit representations of various degrees only for a
local portion of a proof [66, 67].) We wish to avoid this as much as possible.
The heart of closure conversion is computing the free variable set of a function. This
operation is easy to define on paper but difficult when using a HOAS representation in
Twelf because we have no easy way of identifying those terms that are actually vari-
ables. In order to implement closure conversion without resorting to explicit contexts,
we instead redefine the algorithm so that it does not need to compute free variable sets.
We do this by orienting closure conversion not around lambdas themselves but around
the sites that bind variables. The resulting algorithm is not one that we would want to
use in a compiler (its performance is quadratic in the size of the term), but it produces
103
the same output. This technique is due to Karl Crary.
is instead represented as
closure x, e.c with venv
where x and e are bound within c. We regard the closure as having type A cont. This
means that the type translation from the CPS language to the CC language is the iden-
tity, which saves us some work in this proof.
The typing conditions for the language are interesting, because they ensure that clo-
sures are indeed closed. Rather than put a condition on the typing rule for ccclosure
(it is “too late” to do so) we instead put a condition on each variable binding site in the
language. This is similar to the linear judgment in the example above:
frozen : (ccval -> ccexp) -> type.
vfrozen : (ccval -> ccval) -> type.
An expression with a free variable x.c is frozen if x does not appear within the body of a
ccclosure. It may appear anywhere else, including the environment part of a closure.
It is easy to specify this in Twelf; the case for closures is:
vf/closure : vfrozen ([x] ccclosure ([a][e] BOD a e) (ENV x))
<- vfrozen ENV.
The variable x is the variable in question. Because the existential variable ENV is applied
to it, it may appear there (but must be recursively frozen). However, BOD is not applied
to x and therefore x cannot occur in it. A variable is also frozen within any expression
or value if it does not occur at all; this will later allow us to create environments that
contain only the free variables and no others:
vf/closed : vfrozen ([x] V).
We use the frozen family of judgments as a well-formedness condition for every
binder. For example, the typing rule for the first projection operation is
104
cco_fst : ccofv V (A c& B) W ->
({v}{ov : ccofv v A W} ccof (C v) W) ->
frozen C ->
ccof (ccfst V C) W.
We also have relations for the frozenness of modal variables within valid values, and
for the frozenness of valid variables within values, expressions, and valid values.
During translation of the CPS cfst to the CC ccfst, we will first recursively trans-
late the body, which may contain clams that become ccclosures. Since cfst binds
a variable, we then crawl over the term to make sure it doesn’t appear in any closures;
we do this by modifying any closure where it does to store the variable in its environ-
ment and project it within the body. We extract from this traversal a derivation of the
variable’s frozenness, which we use to construct the well-typedness of the ccfst.
We therefore define a function that freezes a variable within an expression (and
value, and valid value):
freeze : {N : ccval -> ccexp}
{N’ : ccval -> ccexp}
{F : frozen N’}
type.
%mode freeze +N -N’ -F.
vfreeze : {N : ccval -> ccval}
{N’ : ccval -> ccval}
{F : vfrozen N’}
type.
%mode vfreeze +N -N’ -F.
It takes an expression with a free variable, and returns a new expression (with a free
variable) and a derivation of its frozenness. There are two interesting cases. The first is
when the variable does not occur; we then do nothing:
fz/closed : vfreeze ([v] V) ([v] V) vf/closed.
This is important so that we do not put all bound variables in every environment. We
place this case first in the logic program so that we always prefer it over the others.2 The
other case is where we reach a closure:
fz/closure :
vfreeze ([x:ccval] ccclosure
([a:ccval][e:ccval] BOD a e x) (ENV x))
([x] ccclosure
([a:ccval][e:ccval]
ccfst e [exh:ccval]
ccsnd e [envtail:ccval]
ccleta exh [ex:ccval]
BOD’ a envtail ex)
2
Interestingly, however, the metatheorem holds for any order of the clauses, so we know that a whole
range of strategies are sound, from the most conservative (include every bound variable) to the one that
logic search returns first (include only the actually occurring variables).
105
(ccpair (ccheld x) (ENV’ x)))
(vf/closure (vf/pair (vf/held vf/var) FENV))
<- ({a:ccval}{e:ccval}
freeze ([x] BOD a e x) ([x] BOD’ a e x) _)
<- vfreeze ENV ENV’ FENV.
To freeze a variable x within a closure, we first freeze it recursively within the en-
vironment part, where it is allowed to appear as usual. We then recursively freeze it
within the body—it may appear there as well (inside nested closures). Given the trans-
lated body and environment, we then construct a new closure whose body is closed
with respect to x. We construct a new environment, which is the pair of x and the old
(translated) environment. The variable x is held because it may be typed at another
world (to freeze a valid variable, we use the shamrock modality). The body of the clo-
sure takes an argument (which we leave untouched) and the environment, which will
now have held x as its first component. Inside the body we project out this value,
bind it with leta, and pass it with the argument and the tail of the environment to the
translated body. In this way, we incrementally build up the closure’s environment for
each variable binding we encounter during translation.
To CPS convert the program, we will convert each lambda to a closure with an empty
environment, and perform the freezing procedure for each binder we see. This transla-
tion will be given on typing derivations as usual, so we will first need a number of easy
lemmas about the well-behavedness of freezing.
Lemmas
The first family of lemmas state that freezing preserves the closedness of a term:
permaclosed :
{F : {v:ccval} freeze ([x] N x) ([x] N’ v x) (Z v)}
{E : {v} {x} ccexp-eq (N’’ x) (N’ v x)}
type.
%mode permaclosed +F -E.
This lemma states that if, in some context where there is a bound variable v, we
freeze x.N (which does not mention v) to get x.N 0 , that N 0 does not mention v either.
We state this by returning an equality between N 0 and an N 00 where N 00 can not mention
v. Unfortunately we need many versions of this lemma: n ∗ f where n is the number
of kinds of variables that might be bound (two: valid or mobile), and f is the number
of freezing operations we have (six: n times the number of syntactic classes, which is
three). This combinatoric explosion is what accounts for most of the bulk of this proof,
even though the lemmas are quite easy. (Polymorphism or automatic theorem proving
would help reduce the repetition, if they were to be implemented in Twelf.)
This lemma is used to prove another, that freezing preserves the frozenness of a term:
permafrost :
{ZN : {v:ccval} frozen ([y] N v y)}
{FN : {y:ccval} freeze ([v] N v y) ([v] N’ v y) (F y)}
106
{ZN’: {v:ccval} frozen ([y] N’ v y)}
type.
%mode permafrost +ZN +FN -ZN’.
Again we need 12 lemmas of this form. We prove that if we have a term frozen with
respect to the variable y, and we freeze some other variable y within it, then the term is
still frozen with respect to y.
Next, we prove that freezing preserves well-formedness of expressions (and values,
and valid values):
freeze/ok : {WN : {x}{xok:ccofv x A W} ccof (N x) W’}
{D : freeze N N’ F}
{WN’ : {x}{xok:ccofv x A W} ccof (N’ x) W’}
type.
%mode freeze/ok +WN +D -WN’.
This lemma is simple: If we have a well formed expression with a free variable, and
freeze it, then the result is also well-formed. Finally, we have an effectiveness lemma
for each of the freezing procedures:
freeze-gimme : {N : ccval -> ccexp} {Z : freeze N N’ F} type.
Translation
We can now define the translation on typing derivations. Because the translation on
types is the identity, it simply takes a CPS typing derivation and returns a CC typing
derivation at the same type.
cc : {D : cof M W}
{D’ : ccof M’ W}
type.
%mode cc +D -D’.
ccv : {D : cofv V A W}
{D’ : ccofv V’ A W}
type.
%mode ccv +D -D’.
To translate a binder like cfst,
- : cc (co_fst WV WN) (cco_fst WV’ WN’’ F)
<- ccv WV WV’
<- ({x}{wx}{x’}{wx’ : ccofv x’ A W}{thm : ccv wx wx’}
cc (WN x wx) (WN’ x’ wx’))
<- freeze-gimme M’ (Z : freeze _ _ F)
<- freeze/ok WN’ Z WN’’.
we translate the values and body recursively, getting derivations that they are well-
typed, WV’ and WN’. We then use the effectiveness of freeze to see that we can freeze
the bound variable within the body. Using freeze/ok we know that this frozen body
is also well-typed, so we reassemble the fst and we are done.
107
When we encounter a lambda, we translate it to a closure with an empty environ-
ment:
- : ccv (cov_lam [x][wx] WM x wx) (ccov_closure ccov_unit
([x][xof][e][eof] WM’’ x xof)
([e] f/closed)
([x] FwrtBOD))
<- ({x}{wx}{x’}{wx’ : ccofv x’ A W}{thm : ccv wx wx’}
cc (WM x wx) (WM’ x’ wx’ : ccof (M’ x’) W))
<- freeze-gimme M’ (Z : freeze _ _ FwrtBOD)
<- freeze/ok WM’ Z WM’’.
For a closure to be well-formed (constant ccov closure) its environment must be
well-formed, the body must be well-formed assuming a well-typed argument and en-
vironment, and the body must be frozen with respect to its environment and argument.
These are easy to establish because at this point the environment is empty (unit) and
unused in the body.
Finally, we translate the go construct to its closure-converted counterpart. In the last
section we took the shortcut of recursing on λ y.c where y is an unused variable of type
unit. If we did this here we would need a more complex induction metric because clam
([y] C) is not a subterm of cgo W V C. Instead, we build in the case for lambdas
above, specialized to this use:
- : cc (co_go WA WC) (cco_go WA’ (ccov_closure ccov_unit
([x][xof][e][eof] WC’)
([x] f/closed)
([x] f/closed)))
<- ccv WA WA’
<- cc WC (WC’ : ccof M’ W).
Here, building the closure is easier because neither the argument nor environment are
used within the body.
The rest of the cases follow the same basic pattern, and we can then verify that the
relations are total, giving us the desired result. The full proof (which is lengthy mostly
because of the lemmas) appears in Appendix A.8.7. 2
4.8 Conclusion
In this chapter I have presented an idealization of the first few phases of typed compila-
tion for a modally-typed programming language. We began with the external language
and eliminated derived forms by elaborating to a simpler internal language. We then
converted to a continuation passing style, which sequences primitive operations and
represents the stack behavior of function calls explicitly. Finally, we performed closure
conversion to allow us to represent lexically-scoped, nested functions.
We formalized each of these languages in Twelf, as well as the translations between
them, and proved the static correctness of those translations. For the internal language
and CPS languages we gave dynamic semantics and proved that the type system is
108
sound. It would be possible to continue further, but as we get deeper into compilation,
the languages and algorithms become more difficult to express (such as we found for
closure conversion) and less like the implementation we will ultimately use, leading to
diminishing intellectual returns on formalization.
Therefore, we now turn our focus to the actual implementation of ML5, which is the
subject of the next chapter.
109
110
Chapter 5
ML5 is a programming language for distributed computing [88], based on the modal
logics presented in Chapter 3. It is an integration of those primitive constructs into a full-
fledged ML-style programming language. Because the language constructs are derived
from a propositions-as-types view of logic, they integrate cleanly with ML (which is
itself based on a compatible interpretation of propositional logic).
The ML5 implementation is based on the compilation strategy studied in Chapter 4.
It type-checks and translates an ML5 source program into low-level code for each of the
hosts involved in a computation, and provides a substrate on which that code can be
run. Currently, the implementation is specialized to web programming, a particular ap-
plication of distributed computing where there are exactly two hosts: the web browser
and the web server. With only some small changes to the runtime it could be extended
to networks of arbitrary size. Therefore, I will discuss the language in its full generality.
In the implementation we are concerned with the usability of the language and with
practical considerations of running it on real computers and networks. This will bring
up some issues that we have not yet encountered: type and validity inference in the
source language, network signatures, exceptions, optimizations to produce more effi-
cient code, the runtime system, and marshaling. These will make the implementation
substantially more complex than the idealized compiler from the previous chapter. For
this reason the argument for its correctness is informal, and the descriptions of the trans-
lations in this chapter are less rigorous. On the other hand, I evaluate the implementa-
tion by building applications in it; these are described in Chapter 6.
Finally, this compiler for an ML-like language is the seventh that I have worked on
or written. Writing similar programs over and over encourages experimentation, to
counter problems experienced in previous iterations and to reduce monotony. For this
compiler (particularly in its later phases) I have experimented with ML programming
techniques for the development of type-directed compilers. Although these are not
specific to the modal setting, I nonetheless spend some time explaining them because I
believe them to be worthwhile techniques.
This chapter is organized as follows. I begin by describing the ML5 language by ex-
ample, using web programming as the application domain (Section 5.1). I then broadly
111
describe the compiler’s architecture and the marshaling strategy, since it has a substan-
tial impact on the way the compiler is designed (Section 5.2.1). After this, I present the
implementation following the route that code takes through the compiler. This starts
with the front-end (Section 5.3), which comprises the parser, elaborator, and internal
language. We then convert to the CPS language (Section 5.4.2). This language has an
interesting implementation (Section 5.4.1) and interface for writing type-directed trans-
formations (Section 5.4.3). It is also where most of the relevant work of the compiler
takes place, from type representation (Section 5.4.5) to closure conversion (Section 5.4.6)
and hoisting (Section 5.4.8) to optimizations (Section 5.4.4). Finally, we generate code
for the hosts involved in the computation (Section 5.4.9). The code is supported by
a runtime system for the client (Section 5.5.3) and a web server and execution engine
(Section 5.5.1) for the server. These parts communicate using a marshaled data format
(Section 5.5.4). I summarize in Section 5.6 before presenting some of the applications in
the following chapter.
5.1 ML5
ML5 closely resembles Standard ML [77] in syntax and semantics. Its type system is, of
course, based upon the modal and validity typing judgments introduced in Chapter 3.
For simplicity, I do not include a module system. I believe that the modal type theory is
compatible and orthogonal. To ensure that the implementation does not abuse the fact
that there are no modules, I support abstract types via the network signature mecha-
nism. This gives a straightforward path to support for separate compilation [129, 130]
and suggests how modules could be integrated.
Essentially all the rest of Standard ML is supported, from mutually-recursive func-
tions to pattern matching, datatypes, extensible types and exceptions. I have also made
minor changes to excise warts or add incidental features, as is the prerogative of the
auteur.
ML5 is implemented via elaboration, so to give a semantics at any level of formality
requires introducing the intermediate language. Rather than jump into the details, I
start with an informal tour of the external language by example. This includes showing
what happens when a program is compiled and run. After that, I begin describing
the implementation, which includes a more formal account of ML5’s type system via
elaboration.
112
extern val alert : string -> unit @ home
extern val server : server addr @ home
fun showversion () =
let val s = from server
get version ()
in
alert [Server’s version is: [s]]
end
do showversion ()
end
Every ML5 program is wrapped in the syntax unit. . . end to delineate it as a com-
pilation unit. Only one compilation unit is currently supported. The body of a compi-
lation unit is a series of declarations. We begin by importing the standard header file
that includes a number of common declarations. This includes declarations of the list,
order, and option types, as well as primitive operations such as integer math, access
to arrays, strings, and references, etc.
We then describe what we need to know about the network by using extern dec-
larations; this is the network signature. First, we declare a world (host) called server.
When we attempt to run the program, there must be a host that actually exists called
server or the program will not be able to run. It will be the runtime system’s respon-
sibility to ensure this and to resolve the name “server” to a particular machine. The
keyword bytecode indicates the worldkind of the world, which tells us what kind of
low-level code it expects. The worldkind bytecode is a simple interpreted language
that runs within the ML5 web server (Section 5.5.1); the other available worldkind is
javascript for producing JavaScript [32] to run in a web browser. The set of avail-
able worldkinds is only limited by what code generators are actually available in the
compiler, and otherwise has no meaning in the language’s semantics.
There must be at least one world in order for the program to mean anything; this
world, where the program begins execution, is called home and is provided in the initial
environment. For web programming, this is the web client (web browser) and so it is a
javascript world.
Once we have declared the existence of worlds we can declare the existence of re-
sources at those worlds. (It is also possible to declare globally available resources and
abstract types. These will be discussed in Section 5.1.3.) The extern val declaration
asserts the existence of a value with the specified type and world, and binds an ML5
variable to it. The value’s name is assumed to be the same as the ML5 identifier, unless
the long syntax is used:
113
(Here version is a label indicating which resource we are making reference to, and v
is the ML5 variable bound.) If these resources do not actually exist, then the program
will not be able to run; it is the responsibility of the runtime system to resolve them.
We declare that there is a function on the server that returns its version, and that
there is a function on the client that displays an alert message to the user. We also declare
that on the client we have the address of the server. The namespace for worlds, types,
and values are distinct, so the convention is to also call this server. To be clear, we
have imported a local resource whose label is server and bound it to an ML5 variable
called server; it is an address for the world also called server.
Now that we have described what we require of the network, we can write code
using those resources. We define a function showversion that displays the server’s
version on the client. The from address get expression construct is the get construct
from Lambda 5. Here server is the address of the world we wish to contact, and the
expression is a call to the version function that exists on the server. We bind the result
to a variable at home called s. The square brackets are ML5’s alternative string literal
syntax. As an ML5 expression, square brackets delimit a string; within a string, they
delimit an ML5 expression which must have type string. These brackets properly
nest, so here we build up a string containing the value of s. We pass this to alert
which will display it on the client. The do declaration simply evaluates an expression
and ignores the result; here we use it to invoke the function on the client to start the
program.
114
inside of a tiny stub HTML page. The client runtime creates a network connection with
the server that it uses to exchange data with it, and begins executing the application
code. For this example, the code calls back to the server and causes it to run some code
that calls the version resource. The server code then calls to the client and causes it to
run code that uses the alert resource to display the version string.
115
5.1.3 Interacting with the environment
Let’s extend our example to make use of more interesting local resources, which will
exercise the network signature mechanism.
We saw how we could declare worlds and import simple resources from them. In the
domain of web programming we know the set of worlds (the server which runs byte-
code and home, the client, which runs JavaScript). These worlds are declared for us in
the standard header, along with their addresses:
extern bytecode world server
extern javascript world home
116
#document
<html>
<body>
<p>Please enter your name:
<input type="text" name="nom" /> HTML
<input type="submit" value="go" />
</p>
</body> HEAD BODY
</html>
Figure 5.1: An example HTML document and its Document Object Model tree. There
are two kinds of nodes, elements and text nodes. Elements may have attributes (such
as for the INPUT nodes) and children. Text nodes are shown here with a double outline;
most consist only of whitespace. The DOM is an abstract representation, correcting for
syntactic and semantic errors in the input (such as the missing <HEAD> in this docu-
ment).
117
extern val [Link] :
string -> [Link] @ home = lc_document_getelementbyid
extern val [Link] :
[Link] * string -> string @ home = lc_domgetobj
extern val [Link] :
[Link] * string * string -> unit @ home = lc_domsetobj
Each of these is supported by a small function in the runtime system that imple-
ments its behavior, because these are actually member method calls on nodes. The first
retrieves a DOM node by its name, given as a string. The [Link] function al-
lows us to retrieve a property of a node, named by a string and whose value is a string.
(Unfortunately, many things in JavaScript are represented by strings. A more structured
interface to the DOM would be desirable, but it is outside the scope of this thesis.) For
example, we can change the contents of a form field (<input> element) named abox
on the page:
val b = [Link] [abox]
do [Link] (b, [value], [hello])
In order for us to interact with page elements they must already exist in the page;
Server 5 provides us with a document that has one element called page. We can begin
the application by creating elements within the page. A convenient way to do this is
to set the innerHTML property, which renders HTML into a DOM tree. For example,
this program creates a page with an input box and then displays its contents in an alert
message:
unit
import "[Link]"
import "[Link]"
do [Link]
([Link] [page],
[innerHTML],
[<input id="abox"
value="hello, world" />])
end
The header file "[Link]" contains the extern declarations above and others for
manipulating the DOM.
Say
To make our application interactive, we can attach handlers to DOM elements that ex-
ecute code when events occur. For example, we can create a span of text that reacts to
118
mouse click events:
(* ... *)
do [Link]
([Link] [page],
[innerHTML],
[<span onClick="alert(’clicked!’);">click me</span>])
The onClick attribute of a DOM element is JavaScript code to be executed when the
element is clicked. In this example the JavaScript code calls the familiar alert function.
We’d prefer to use ML5 to write event handlers, so we can use the say construct to
produce JavaScript (as a string) from an ML5 expression:
(* ... *)
fun handle_click () = alert [clicked!]
do [Link]
([Link] [page],
[innerHTML],
[<span onClick="[say handle_click ()]">\
click me</span>])
This program has the same behavior as the above. The body of the say construct
can be any ML5 expression (here it is a function call), and the semantics are as follows:
At the time the say is evaluated, the body is suspended and converted to a JavaScript
expression. When that JavaScript expression is evaluated (because it is used as a handler
attribute), the suspended ML5 expression is evaluated as a new thread. This allows us
to dynamically generate handlers and to modify the behavior of the page at runtime.
Some events have parameters. For example, the onKeyUp event for input boxes
indicates the key that was pressed. Because the way that events work in JavaScript is
quite irregular (Section 5.4.9), we must explicitly specify the event properties that we
want and bind them to ML5 variables. For example, we can detect when the enter key
is pressed in a form input:
do [Link]
([Link] [page],
[innerHTML],
[<input type="text"
onkeyup="[say { [Link] = c }
case c of
?\r => alert [pressed enter!]
| _ => ()]"
/>])
The syntax ?x is the character constant x, and \r is the escape sequence for the
return character.
Again, it would be nice to provide a more structured interface for interactivity that
allows us to use the type system to prevent mistakes. As it stands, the programmer
can modify the string that results from a say (or simply write his own JavaScript that
corrupts the ML5 runtime environment). Given the nature of JavaScript, this would
119
take a bit of work to accomplish.
The interfaces in the example applications are all built around this way of interacting
with the DOM on the client. On the server, most applications interface with a simple
database described using the network signature mechanism. However, the purpose
of ML5 is not simply to provide glue to resources but to give an expressive language
for doing computation with them. In the next section I tour the ML-like features to
highlight the differences with Standard ML and ML5, mention how they interact with
the modal type system, and to set the stage for a discussion of their implementation.
120
datatype void =
In ML5, datatype declarations are transparent, meaning that they do not define new
types like Standard ML [137]. Rather, they declare the constructors and bind type names
to primitive recursive sum types that “already exist.” This means that if the programmer
declares the same datatype (up to reordering of constructors) in two places, then those
types will be equivalent. It also makes it easier to perform optimizations, since we have
more information about the representation when its type is not abstract.
Because datatypes are transparent, constructors can safely be bound in the valid
context—they could just as well have been defined anywhere. This is important so
that different worlds can share common data structures (and particularly important for
types like option and bool).
The other way that datatypes require special consideration in ML5 is with regard
to the mobile judgment. We allow a datatype to be mobile when every datatype in its
bundle of mutually-recursive types consists only of arms with mobile types. This will
be made precise when we discuss the ML5 mobile judgment in Section 5.3.2.
Extensible types
Datatypes are closed disjoint unions where all of the possibilities (tags) are known at the
time of declaration. ML5 also supports extensible types, which are an “open” alternative
to datatypes where new tags can be created dynamically. Standard ML has one such
type, exn (which also happens to be the type of exception tags). In addition to exn,
ML5 supports the creation of new extensible types. For example, this declaration makes
a new extensible type called exp:
tagtype exp
We can then declare new tags for this type:
newtag Bool of bool in exp
newtag If of exp * exp * exp in exp
They are consumed by pattern matching:
fun eval (e : exp) =
case e of
If (c, e1, e2) =>
(case eval c of
Bool true => eval e1
| Bool false => eval e2)
| Bool b => Bool b
Interestingly, because extensible types are abstract (as opposed to transparent, like
datatypes) any extensible type is mobile. This turns out to be required to make the
implementation of exceptions work. The reason that this is safe is that any given con-
structor (tag) for an arm of an extensible type is not necessarily available at all worlds.
For example, given the above declaration of exp and Bool, the following program is
ill-typed:
121
put x = If(Bool true, Bool false, Bool true)
do from server
get case x of
Bool b => b
The put is allowed because its body has mobile type (exp). However, the case anal-
ysis is ill-typed because the constructor Bool is modally typed at the client, not the
server. It is possible, however, to declare all or some of the tags in an extensible type to
be valid. For instance, this program is well-typed:
tagtype exp
newvtag Bool of bool in exp
newvtag If of exp * exp * exp in exp
newtag Ref of exp ref in exp
Exceptions
ML5 provides an exception mechanism for non-local control flow just like Stan-
dard ML’s. An extensible type exn with a valid tag Match is part of the initial envi-
ronment. New tags can be created by
exception Fail of string
which is equivalent to
122
newtag Fail of string in exn
(It is also possible to declare valid exceptions with vexception.) An exception is
thrown by raise and caught by handle:
( raise Fail "oops" )
handle Fail s => alert [failure: [s]]
| Match => alert [match?]
and an unmatched exception is automatically reraised. The exn type, like other ex-
tensible types, is mobile. We use this fact during compilation (Section 5.4.2). The only
interesting thing about exceptions in ML5 is their interaction with get: When we raise
an exception in the body of a get, the exceptional control flow should propagate back
to the calling world. The implementation of this happens in CPS conversion when we
translate get to go (Section 5.4.2).
‘When’ patterns. ML5 also supports a pattern that may perform computation, which
is called a when pattern. The syntax is
where e is an expression. This expression, which must have function type, is applied to
the case object and the result is matched against the pattern p. If the expression raises
Match, then the pattern match also fails. This allows us to implement something like
views [139] in a lightweight way; for example, we can pattern match against integers as
if they are natural numbers as follows:
123
fun Z 0 = ()
fun S 0 = raise Match
| S n = n - 1
First-class continuations
124
Another use of continuations is for “early exits” from code. This function computes
the product of a list of integers but stops if it sees a zero:
fun product l =
letcc ret
in list-foldl (fn (0, _) => throw 0 to ret
| (m, n) => m * n) 1 l
end
In ML, exceptions are typically used for this purpose. This idiom is more direct and
more efficient because it does not require the creation of tags or dispatch on them.
Continuations are usually employed in these two stylized ways, but sometimes they
are useful in their generality for the creation of user interface prompts. The chat appli-
cation (Section 6.2) has such an example.
5.1.5 Summary
This concludes the high-level tour of ML5. The language contains other minor features
and I have not given an explanation of the constructs that are common with ML. Such
a discussion would be better suited to a programming language manual; the purpose
of this dissertation is to investigate the type theory and the mechanics of its implemen-
tation. Therefore, let us now shift gears and transition to a systematic account of the
implementation and the language’s definition via elaboration.
5.2 ML5/pgh
ML5/pgh (“ML5 of Pittsburgh”) is the compiler for ML5 and—in the absence of a for-
mal semantics for the language—its definition qua reference implementation.
We have already seen several examples, so let us jump straight into the details of the
implementation. We’ll begin with a description of the compiler’s design, particularly
with regard to data marshaling, since this will have a pervasive effect on the way we
compile programs.
125
of closure conversion does this whenever it builds an environment containing remote
resources, for example. This means that we need to be able to marshal any kind of value.
Marshaling “plain old data”—strings and integers and aggregations of them—is
easy. We will be able to marshal code easily as well, because after closure conversion
and hoisting, every piece of code can be identified by a global label that can be rep-
resented as an integer. The hosts involved in a computation will agree on this set of
labels and receive the code for them before the program begins. A problem is posed by
polymorphism, however: How do we marshal some value whose type we don’t know?
For example, consider this version of the polymorphic identity function that prints a
message on the server each time it is called:
fun (a) id (x : a) =
let in
from server get display "called id...\n";
x
end
(The syntax (a) binds a type variable so that we can use it in the ascription for x.)
After CPS and closure conversion, the value of x will need to make a round-trip to
the server and back, so we must marshal and unmarshal it. However, this function
can be called with any type a, so we don’t know ahead of time what shape the value
of x might have and therefore how to marshal it! There are two potential solutions
to this problem. The first is to use a uniform representation for values that allows us to
programmatically discover their shape at runtime. This essentially requires adding a tag
to every value. Many language implementations work this way, because there are other
reasons (garbage collection being a common one) that an implementation needs this
information at runtime. (The Grid/ML compiler [85] did this for the sake of marshaling,
as well.) The other way to do it is to have data (tags) that describe the shape of the
values, but to disembody them from the values themselves. Conceptually, these data are
the run-time representations of types. For example, we can rewrite the above function
to take another parameter:
fun (a) id’ (tag : a rep, x : a) = (* ... *)
The type a rep is a singleton type, containing the run-time value that represents the
type a. If we have this run-time representation around, then it can be an additional
input to the marshaling routine and guide its processing of x. Decoupling tags from
values in this way has a few advantages. First, it constrains our implementation less,
because we can use native representations for values and treat the tag data as ancillary.
Second, when the type representations are not needed (because we do not attempt to
marshal), we can eliminate them from the code. This means that the programmer does
not need to pay a performance penalty for the feature when he is not using it.
In the ML5/pgh internals we will perform a type representation transformation (Sec-
tions 5.4.5 and 5.4.7) to make sure that whenever we marshal a value, we have a repre-
sentation of that value’s type. Because the modal type system assigns both types and
worlds to values, we additionally represent worlds at run-time. This is important be-
cause it allows us to specialize the representation of some value given its world. For
126
ASCII TOK EL
tokenizer parser desugar
IL CPS
elabora!on CPS conversion
JS
hoist codegen js codegen opts write
B5 ASCII
bytecode codegen write
op!miza!ons
type-checking
Figure 5.2: The architecture of the ML5/pgh compiler.
example, local resources like DOM handles cannot be easily marshaled, because we do
not have control over their representation. Instead, we can do the following. When ex-
ecuting at home, we represent values of type [Link] @ home as native JavaScript
DOM nodes. When executing on another world server, we represent a value of type
[Link] @ home as an integer. This integer is an index into a table of values at home.
When at home we marshal a DOM node value, we insert it in this table and send an
integer in its place. When at home we unmarshal a [Link] @ home, which is repre-
sented as an integer, we look it up in the table and return the native pointer. Anywhere
else in the network, a DOM node is represented as an integer. (This process, called desic-
cation and reconstitution, is detailed in Section 5.5.4.) To use this representation we must
know the world of a value as we marshal it; because of world polymorphism we must
therefore represent worlds as we do types.
The overall architecture of the compiler appears in Figure 5.2. The front-end (Sec-
tion 5.3) lexes, parses, and elaborates the source text into the internal language. After a
phase of optimization we convert to CPS, where most of the compiler’s work is done.
We insert the type representations and perform closure conversion, maintaining the in-
variant that every type and world variable is paired with a representation for it. We then
reify these representations into actual data. Hoisting pulls each closed piece of code out
to the top-level and gives it a global label. Code generation produces code for each of
these labels, depending on what world(s) it needs to be defined for. For each world we
output either JavaScript or Server 5 bytecode.
It is worth mentioning a few other principles that guide the implementation. First,
127
it is a type-directed compiler, meaning that each of these languages (up to code gener-
ation) has (possibly implicit) type information associated with it. Second, because this
is a prototype implementation not intended for production use, not much concern has
been given to the performance of the compiler itself. Instead, an emphasis has been
placed on simple and correct code. For example, after every phase in the CPS language,
we type-check the entire program again. All transformations are done on functional
data structures. Additionally, the interface to the CPS language has been engineered to
automatically alpha-vary code to prevent bugs resulting from variable capture.
5.3 Front-end
Now let us discuss the phases of the implementation. The front-end of the compiler is
the interface with the user: It reads the source program, type checks it (providing error
messages if it is ill-formed), and produces intermediate language code that the rest of
the compiler works on.
5.3.1 Parsing
Naturally, the first step is the parser, which reads the input files and produces a data
structure representing the external language’s abstract syntax. Most parsers are quite
boring, specifying the language’s grammar in BNF and using a tool like yacc to produce
a program implementing the grammar. ML5’s parser is somewhat unusual in that it is
written with parser combinators [42, 58]. Therefore I will discuss it briefly.
Parser combinators are a way of compositionally hand-writing parsers in functional
languages. We begin by defining a type
(α, β) parser
which can be thought of as a function that parses some prefix of a β stream into an α,
returning the remainder of the stream as well. Additionally, the parser may fail. Such
parsers may be composed; for example, the sequential parser
successively applies the two parsers to the same stream and returns a pair of the results.
Through careful design of the names of the combinators, the program implementing the
parser resembles the grammar of the object language. For example, here is a fragment
of the ML5 parser for declarations:
fun regulardec G =
!!(alt[‘VAL >> tyvars && (call G pat suchthat irrefutable) &&
‘EQUALS && call G exp
wth (fn (tv, (pat, (_, e))) => Bind(Val, tv, pat, e)),
128
‘TAGTYPE >> id wth Tagtype,
‘NEWTAG >> expid && opt (‘OF >> typ) && ‘IN && id
wth (fn (i,(to,(_,ty))) => Newtag (i, false, to, ty)),
(* ... *)])
The particular meaning of the combinators is not important here; simply observe
that each case begins with a keyword token (like val or tagtype), then the rule in the
grammar for that construct, which refers to other syntactic classes as parser functions
defined above (or mutually recursive with this one). It is not difficult to give error
messages, at least for the grammar of ML5—after seeing a keyword like val, if we do
not successfully parse a val declaration, we can abort and report the current location
to the user along with a message. This is the purpose of the second line starting with
‘VAL above.
One nice thing about parsing combinators is that we have the whole power of our
general purpose programming language at our disposal. This means that we are not
constrained by the particular algorithm that our parser-generator uses. (This is par-
ticularly relevant because most real programming languages are not actually context-
free.) In the case of ML5, this allows us to properly parse fixity declarations like infix
by passing along a parsing context to each parsing function. (This is the argument G
above.) Most ML compilers instead parse a different grammar and resolve fixity after
the fact. ML5’s nested string constants are implemented with no particular trouble. We
even properly parse Standard ML’s notoriously difficult nested pattern match [79]:
fun hard 0 =
case x of
1 => 2
| 3 => 4
| hard 5 = 6
Therefore, as long as some care is given to error messages, I believe that parser com-
binators are a good way to implement grammars for ML. Performance is fine, there is no
metaprogramming or dependency on other tools, writing the code is a joy, and there is
no danger of getting “stuck” being unable to express the desired grammar in a restricted
language.
Syntactic sugar
After parsing, there is a small pass to regularize the language by eliminating some syn-
tactic sugar. For example, in the translated code
datatype bool = true | false
129
we rewrite the declaration of bool to take zero type arguments, and to be applied
to zero arguments wherever it is used. Now we can treat every datatype declaration
uniformly as being polymorphic (perhaps in zero arguments). More importantly, we
rewrite the variable patterns in the argument of not to be application patterns. This
makes pattern compilation much easier because all tag dispatches will be application
patterns. We could rewrite these constructors to take “of unit” so that every con-
structor carries a type; instead we internally support a special “of --” and a corre-
sponding application pattern to permit more efficient representations of non-carrying
constructors.
130
worlds w ::= ω|w
monotypes A ::= α | {`1 : A1 , . . . , `n : An } | [`1 : A?1 , . . . , `n : A?n ]
| R | [R0 , . . . , Rn ]
| A cont | A ref | A array | A tagα
| A at w | ω A | w addr
| πi (µ α0 .A0 , . . . αn .An )
optional type A? ::= —|A
arrow R ::= (A1 , . . . , An ) → A
polytypes P ::= ∀ω1 . . . ωm .∀α1 . . . αn .A
kinds K ::= Reg | Ext
Γ(α) = K Γ(ω) = world
Γ ` α okA Γ ` w okw Γ ` ω okw
i ∈ {0, . . . , n}
Γ, α0 :: Reg, . . . , αn :: Reg ` A0 okA
..
Γ ` A okA .
Γ ` A1 okA . . . Γ ` An okA Γ, αn :: Reg, . . . , αn :: Reg ` An okA
Γ ` (A1 , . . . , An ) → A okR Γ ` πi (µ α0 .A0 , . . . αn .An ) okA
Figure 5.3: Types of the ML5 internal language. Each syntactic class C has a well-
formedness judgment okC ; the rules are given in this figure because they illustrate the
binding structure of types. In the remainder of the discussion, however, we will assume
that types are well-formed for brevity.
131
Γ, α mobile ` α mobile Γ ` A at w mobile
Γ ` A mobile
Γ ` A tagα mobile
Figure 5.4: The mobile judgment for ML5. The context Γ here contains assumptions of
the form α mobile. This context will include base types like int and string, as well as
any extensible type.
Terms
The IL term language appears in Figure 5.5. As before, we have a syntactic distinc-
tion between values and expressions. This is important because of some syntactic value
132
vals v ::= ~ w
xhA; ~ i | uhA; ~ w ~ i | n | "string"
| {{`1 = v1 , . . . , `m = vm }} | inj`A v | inj`A —
| heldw v | sham ω.v | rollA v
| fns (f1 (x11 :A11 , . . . , x1m :A1m ) = M1 , . . .) | fsel v.n
exps M, N ::= value v | M (M1 , . . . , Mm ) | {`1 = M1 , . . . , `m = Mm }
| #` M | raiseA M | M handle x.N
| (M ; N ) | let D in M | unroll M
| rollA M | say (`1 :A1 , . . . , `m :Am ) M
| get[w; M ] N | throwA M to N | letccA x inN
| tag M with N
| untag M with N of( yes ⇒ x.N1 | no ⇒ N2 )
| primapp phAi( ~ M ~ ) | jointext M ~
` `
| injA v | injA —
| sumcase M of (`1 ⇒ x.N1 | . . . | `m ⇒ [Link] | ⇒ N )
| intcase M of (n1 ⇒ N1 | . . . | nm ⇒ Nm | ⇒ N )
integers n ::= 0 | 1 | − 1 | ...
decls D ::= do M | tagtype α | newtagα x of A
| val x = M
| polyval (~ α, ω ~) x = v
| polyput (~ α, ω ~) u = v
| polyleta (~ α, ω ~) x = v
| polyletsham (~ α, ω
~) u = v
| extern world J w
| extern type α = `
| extern val u ∼ ∀ω.~ω .∀~ α.A = `
| extern val x : ∀~ω .∀~ α.A @ w = `
worldkinds J
Figure 5.5: The ML5 internal language terms. We distinguish values and expressions as
before, with the boldface version of a construct being the value form. Many constructs
take a list of arguments; we use the syntax α
~ (for example) to denote a sequence of type
variables α.
133
restrictions (for polymorphism and the body of a sham constructor). Many of the con-
structs are self-explanatory, having appeared before or having direct analogues in ML;
I will only explain the ones that are new.
Every variable x (which is bound at a polytype) is applied to a sequence of types and
worlds, since terms are assigned monotypes. The same is true for valid variables u. The
typing rules appear in Figure 5.6 along with the other rules for values. Most of these are
straightforward. There are two forms of injection into sum types, for those constructors
that carry a type and those that don’t. An inductive type πi (µ . . .) can be unrolled one
level by selecting the ith component and substituting all of the inductive projections for
its free type variables. The roll construct is the injection into an inductive type; for it
to be well-formed, the value must be of the unrolled type. The value for a bundle of
mutually-recursive functions, fns, is complicated only because of the mutual recursion
and multiple arguments. Each function is bound within the body of all of the functions.
The fsel value retrieves an individual function from a bundle; this is how we get a value
of arrow type that we can call.
The typing rules for expressions appear in Figure 5.7. Most of these are straightfor-
ward as well. The rule for let uses the typing judgment for declarations,
w
Γ`D Γ0
which means that in the context Γ, the declaration D yields at w the new bindings in
Γ0 . The rules for declarations are discussed below. The unroll expression exposes one
level of an inductive type by substituting projections from the bundle for all of the free
variables. The say construct names the event fields that it expects, and their types,
and then supplies a continuation that takes those events as a record. It can only be
run at the constant world home, which is the client web browser, although this could
be relaxed to any world whose worldkind is javascript if we supported more than
two worlds. The jointext construct is primitive string concatenation. There are also
primitives for many other operations, such as the allocation of references and arrays,
subscripting and comparing strings and other base types, integer math, comparisons,
etc.. Their typing rules are not interesting so I do not give them here. A value can be
tagged with a compatible tag; the result is some extensible type. (All extensible types
are type variables.) To deconstruct a value of extensible type, we test it against a specific
tag. If the tag matches, then we retrieve the value embedded within it and proceed along
the yes branch; if not then we get nothing and proceed along the no branch. There are
two other kinds of case analysis as well. The sumcase construct destructs a labeled
n-ary sum, by providing some subset of the labels as distinguished cases and a default
in case none of them match. Within each of the distinguished cases a variable is bound
to the carried value, unless the label is a non-carrier. We also have intcase for efficient
dispatch on integers; its rule is similar except that no variable is bound.
Declarations are interesting because they introduce polymorphic and valid bindings.
The typing rules for declarations appear in Figure 5.8. The tagtype declaration binds
a new type variable whose kind is Ext. Such variables can be used to make member
tags with newtag. We have a basic val binding that evaluates an expression and binds
134
Γ = Γ1 , x:∀ω1 . . . ωm .∀α1 . . . αn .B @ w, Γ2 Γ = Γ1 , u∼ω.∀ω1 . . . ωm .∀α1 . . . αn .B, Γ2
~ w ~ ~ w ~
Γ ` xhA, ~ i : [ w~/ω~ ][ A/α~ ]B @ w Γ ` uhA, ~ i : [ w/ω ][ w~/ω~ ][ A/α~ ]B @ w
Γ ` n : int @ w Γ ` s : string @ w
Γ ` v1 : A1 @ w . . . Γ ` vm : Am @ w
Γ ` {{`1 = v1 , . . . , `m = vm }} : {`1 : A1 , . . . `m : Am } @ w
Γ ` v : B @w
A= [`1 : A?1 , . . . , ` : —, . . . , `m : A?m ] A = [`1 : A?1 , . . . , ` : B, . . . , `m : A?m ]
Γ ` inj`A — : A @ w Γ ` inj`A v : A @ w
Γ ` v : A@w Γ, ω world ` v : A @ ω
Γ ` heldw v : A at w @ w0 Γ ` sham ω.v : ω A @ w
Γ ` v : B @w
0≤i≤m
A = πi (µ α0 .A0 , . . . αm .Am )
B = [ π0 (µ α0 .A0 ,...,αm .Am )/α0 ] · · · [ πm (µ α0 .A0 ,...,αm .Am )/αm ]Ai
Γ ` rollA v : A @ w
Γ0 = Γ, f1 :R1 , . . . , fn :Rn
Γ0 , x11 :A11 , . . . , x1m1 :A1m1 ` M1 : B1 @ w R1 = (A11 , . . . , A1m1 ) → B1
..
.
Γ0 , xn1 :An1 , . . . , xnmn :Anmn ` Mn : Bn @ w Rn = (An1 , . . . , Anmn ) → Bn
Γ ` fns (f1 (x11 :A11 , . . . , x1m1 :A1m1 ) = M1 , . . .) : [R1 , . . . , Rn ] @ w
Γ ` v : [R0 , . . . , Rk ] @ w Rn = (A1 , . . . , Am ) → B
Γ ` fsel v.n : (A1 , . . . , Am ) → B @ w
Figure 5.6: Typing rules for ML5 internal language values. Each value is assigned a
monotype and world with the judgment Γ ` v : A @ w.
135
Γ ` M : (A1 , . . . , Am ) → B @ w
Γ ` v : A@w Γ ` N1 : A1 @ w · · · Γ ` N1 : A1 @ w
Γ ` value v : A @ w Γ ` M (N1 , . . . , Nm ) : B @ w
Γ ` M : {`1 : A1 , . . . , ` : A, . . . , `m : Am } @ w
Γ ` #` M : A @ w
Γ ` M : exn @ w Γ ` M : A @ w Γ, x:exn @ w ` N : A @ w
Γ ` raiseA M : A @ w Γ ` M handle x.N : A @ w
w
Γ ` M : B @w Γ ` N : A@w Γ`D Γ0 Γ, Γ0 ` M : C @ w
Γ ` (M ; N ) : A @ w Γ ` let D in M : C @ w
Γ ` M : πi (µ α0 .A0 , . . . αm .Am ) @ w
Γ ` unroll M : [ π0 (µ α0 .A0 ,...,αm .Am )/α0 ] · · · [ πm (µ α0 .A0 ,...,αm .Am )/αm ]Ai @ w
Γ ` M : {1 : A1 , . . . , m : Am } cont @ home
Γ ` say (`1 :A1 , . . . , `m :Am ) M : string @ home
Γ ` M : w0 addr @ w Γ ` N : A @ w Γ ` A mobile
Γ ` get[w0 ; M ] N : A @ w
Γ ` M : B @ w Γ ` N : B cont @ w Γ, x:A cont @ w ` N : A @ w
Γ ` throwA M to N : A @ w Γ ` letccA x in N : A @ w
Γ ` M1 : string @ w · · · Γ ` Mn : string @ w Γ ` N : A tagα @ w Γ ` M : A @ w
Γ ` jointext M1 . . . Mn : string @ w Γ ` tag M with N : α @ w
Γ ` M : α @ w Γ ` N : A tagα @ w Γ, x:A @ w ` N1 : C @ w Γ ` N2 : C @ w
Γ ` untag M with N of yes ⇒ x.N1 | no ⇒ N2 : C @ w
Γ ` M : [`1 : A?1 , . . . , `n : A?n ] @ w Γ ` Ni : C @ w when A?i = —
m≤n Γ ` N : C @w Γ, x:A?i @ w ` Ni : C @ w when A?i 6= —
Γ ` sumcase M of (`1 ⇒ x.N1 | . . . | `m ⇒ [Link] | ⇒ N ) : C @ w
Γ ` M : int @ w Γ ` Ni : C @ w Γ ` N : C @ w
Γ ` intcase M of (n1 ⇒ N1 | . . . | nm ⇒ Nm | ⇒ N ) : C @ w
Figure 5.7: Typing rules for ML5 internal language expressions. Expressions are as-
signed a type and world with the judgment Γ ` M : A @ w. I omit the rules for record,
roll, and inj expressions because they are the same as their value counterparts. There
are many rules for primapp depending on the primitive operation being applied; they
are all straightforward and omitted here for brevity.
136
the resulting value. There is also a polyval binding that is restricted to values and that
produces a polymorphic binding. The familiar put, leta, and letsham constructs
come in this polymorphic form. (When we put an expression in the external language,
we first evaluate the expression with val and then bind the result with no polymor-
phic types or worlds using a degenerate polyval.) The extern world declaration
produces no bindings. This is because it declares the existence of a world constant, not
a variable. Within this abstract presentation of the IL, world constants are drawn from
an unspecified set of constants w, just as integers n are drawn from the set of integers
and string constants are drawn from the set of strings. However, the set of constants is
less obvious than the integers (except that we know this set contains the initial world
home). Therefore we ask the programmer to declare these constants. After checking
that the programmer does not use any constants that were undeclared, we preserve the
set of world constants just so that we can know which world constants to generate code
for (the programmer may never otherwise mention them). They will later be hoisted
out of the program to determine this set; we could just as well have done this during
elaboration. The extern type declaration does bind a variable, as do the modal and
valid versions of extern world.
5.3.3 Elaboration
Elaboration is the process of transforming the external language (EL) abstract syntax
into the internal language (IL), possibly rejecting the program with an error message if
it is ill-formed. Traditionally there are two kinds of elaboration: a declarative semantics
that relates EL programs to IL programs nondeterministically, and an implementation
of those semantics as a syntax-directed transformation with reasonable algorithmic be-
havior. I do not give a complete elaboration semantics in either form, but show some of
the interesting declarative rules and discuss how they are implemented algorithmically.
For the sake of this presentation, I will assume simplified versions of the external
language constructs and only discuss the interesting ones. For example, functions will
take a single argument with no pattern matching. I will ignore non-carrying datatype
and extensible type constructors. I will leave out explicit type variables from val and
fun declarations, since they are not necessary there.
Elaboration is based on two judgments: one for elaborating EL expressions into IL
expressions and values, and one for elaborating EL declarations into IL declarations and
new context entries. The first is written
Γ ` E 99K M : A @ w
where M is the resulting IL expression from evaluating E and A @ w is its IL type and
world. For declarations, we have
0
Γ ` L 99Kw
D Γ | D1 , . . . , Dn
where Γ0 is the new elaboration context entries produced from elaborating the IL decla-
ration L at the world w and D1 through Dn are the IL declarations produced.
137
Γ ` M : A@w
w
Γ ` do M ·
w w
Γ ` tagtype α α::Ext Γ ` newtagα x of A x:A tagα @ w
Γ ` M : A@w Γ, ω ~ ::Reg ` v : A @ w
~ world, α
w w
Γ ` val x = M x:A @ w Γ ` polyval (~
α, ω
~) x = v x:∀~ω .∀~
α.A @ w
Γ, ω ~ ::Reg ` A mobile
~ world, α Γ, ω ~ ::Reg ` v : A @ w
~ world, α
w
Γ ` polyput (~
α, ω
~) u = v u∼∀~ω .∀~
α.A
Γ, ω ~ ::Reg ` v : A at w0 @ w
~ world, α
w
Γ ` polyleta (~
α, ω
~) x = v α.A @ w0
x:∀~ω .∀~
Γ, ω ~ ::Reg ` v :
~ world, α ωA @ w
w
Γ ` polyletsham (~
α, ω
~) u = v u∼ω.∀~ω .∀~
α.A
w w
Γ ` extern world J w0 · Γ ` extern type α = ` α::Reg
w
Γ ` extern val u ∼ ω.∀~ω .∀~
α.A = ` u∼ω.∀~ω .∀~
α.A
w
α.A @ w0 = `
Γ ` extern val x : ∀~ω .∀~ α.A @ w0
x:∀~ω .∀~
Figure 5.8: Typing rules for ML5 internal language declarations. Declarations are
w
checked with the judgment Γ ` D Γ0 , indicating that they may be evaluated at
the world w and produce the new bindings Γ0 .
138
In both cases, the context Γ is an elaboration context, which contains IL hypotheses
and other information we need to perform the translation. This includes information
about the constructor status of identifiers, and abbreviations for type identifiers that we
expand when we encounter them. I will mention these as they are needed.
Let’s begin with the case for EL expression identifiers. We have two rules that might
apply:
Γ(xid ) = ∀ω1 . . . ωm .∀α1 . . . αn .B @ w
~
Γ ` id 99K value xid hA1 , . . . An , w1 , . . . , wm i : [ w~/ω~ ][ A/α~ ]B @ w
Γ(uid ) = ω.∀ω1 . . . ωm .∀α1 . . . αn .B
~
Γ ` id 99K value uid hA1 , . . . An , w1 , . . . , wm i : [ w/ω ][ w~/ω~ ][ A/α~ ]B @ w
The external language does not have different syntax for modal and valid variables,
so an identifier id could be either one. The convention xid gives us the IL modal variable
corresponding to an identifier; if it is bound in the context then this is a modal variable.
Since variable bindings are given polytypes, we nondeterministically apply it to any A ~
and w~ . (In the implementation, these types will actually be determined by type infer-
ence.) The convention uid similarly gives us the IL valid variable; if that is bound, then
we apply it to the polymorphic type and world arguments, and instantiate the world
variable at the current world. Context lookup is arranged such that if the programmer
shadows an identifier standing for one sort of variable with an identifier standing for
the other, the shadowed variable is not found by the Γ(x) operation. (Otherwise, both
rules might apply.)
We can insert a variable binding with the val declaration, for example:
Γ ` E 99K M : A @ w
Γ ` val id = E 99Kw
D xid :A @ w | val xid = M
We produce an IL val binding along with the new context entry for it. (Here A is
actually the polytype ∀.∀.A; that is, with no type or world variables quantified. These
empty quantifiers are omitted for syntactic brevity.) In the case that M is an expression,
this is our only choice. However, if M is a value then we can choose to make the binding
polymorphic or polymorphic and valid.
Γ, ω ~ ::Reg ` E 99K value v : A @ w0
~ world, α
Γ ` val id = E 99KwD xid :∀~α.∀~ω .A @ w0 | polyleta (~
α, ω
~ ) xid = heldw0 v
In this first case, we choose some world and type variables to make the value poly-
morphic in, and a world w0 where it will be typed. The world is unconstrained; this
allows us to (for example) declare functions “ @ server” and “ @ home” at the top-level
in our program without first traveling there. To accomplish the binding at this possibly
remote world, we introduce the at modality with held and immediately eliminate it
with polyleta.
We can also make a binding that is valid:
Γ, ω world, α ~ world ` E 99K value v : A @ ω
~ ::Reg, ω
w
Γ ` val id = E 99KD uid ∼ω.∀~ α.∀~ω .A | polyletsham (~
α, ω
~ ) uid = sham ω.v
139
Γ, ω ~ ::Reg ` E 99K value v : A at w0 @ w
~ world, α
Γ ` leta id = E 99Kw α.∀~ω .A @ w0 | polyleta (~
D xid :∀~ α, ω
~ ) xid = v
Γ ` E 99K M : A at w0 @ w
Γ ` leta id = E 99Kw
D y:A at w0 @ w, xid :A @ w0 | val y = M, polyleta () xid = yhi
Γ ` E 99K M : ω A @ w
Γ ` letsham id = E 99Kw
D y: ω A @ w, uid ∼ω.A | val y = M, polyletsham () uid = yhi
Γ ` E 99K value v : A @ w0
Γ ` hold E 99K heldw0 v : A at w0 @ w
Γ ` E 99K M : A @ w
Γ ` hold E 99K let val y = M in heldw yhi : A at w @ w
Figure 5.9: Elaboration of the at and modalities. Each binding has a polymorphic
and monomorphic version, depending on whether the body is a value or not. In the
monomorphic version, we sequence the evaluation of the expression with val and then
make a degenerate polymorphic binding that quantifies over no type or world vari-
ables. For hold, we might be writing the value held (which allows its body to be at
another world) or the expression hold that introduces the at modality locally. The
sham constructor, on the other hand, requires its body to be a value always.
In this case, we choose polymorphic type and world variables, and a hypothetical
world ω at which to check the value. If it is well-typed there, we wrap it in the ω
modality with sham and immediately eliminate it with polyletsham to create a poly-
morphic valid binding.
Although they are rarely needed (because of the powerful val declaration), we also
give the programmer access to the at and modalities directly. The rules for these
appear in Figure 5.9.
Functions
We treat functions as a form of val declaration in order to use the same mechanism
for polymorphic generalization and validity. The only complication is therefore mutual
recursion. We expand the bundle
140
fun f1(x) = E1
and (* ... *)
and fm(x) = Em
to
val bundle = (fns f1(x) = E1
and (* ... *)
and fm(x) = Em)
val f1 = fsel bundle.0
(* ... *)
val fm = fsel bundle.m
and then elaborate that. (This requires adding a syntax for mutually recursive function
values and projection to the EL abstract syntax, but not to its concrete syntax.) Because
a bundle of functions is a value, and selecting a function from a bundle is a value, these
may all be generalized and/or made valid (if possible).
The IL has both the mutually recursive fns construct and the fsel value so elabora-
tion is totally straightforward; I do not give the rules.
Types
Elaborating types is simple. The EL supports a type abbreviation mechanism,
type (a, b) t = a * b * int
For simplicity, the IL does not have such a construct; we expand them during elabo-
ration. We therefore have another form of hypothesis that can appear in the elaboration
context:
id = λ~α.A
where id is an external language identifier, α
~ are its type arguments, and A is an internal
language type. We expand such abbreviations eagerly when we encounter them.
The rules for type elaboration are given by the judgment
Γ ` T 99KT A
For example, tuples are expanded as records with fields labeled 1 . . . n as in SML:
Γ ` T1 99KT A1 · · · Γ ` Tn 99KT An
Γ ` T1 ∗ · · · ∗ Tn 99KT {1 : A1 , . . . , n : An }
Identifiers are elaborated to type variables and type applications are expanded:
Γ(αid ) :: K
Γ ` id 99KT αid
Γ(id) = λα1 , . . . , αn .BΓ ` T1 99KT A1 · · · Γ ` Tn 99KT An
Γ ` (T1 , . . . , Tn ) id 99KT [ A1/α1 ] · · · [ An/αn ]B
The phase after parsing that eliminated syntactic sugar ensured that all defined types
appear as applications (possibly to zero arguments).
141
Datatypes
A datatype is elaborated into a µ type whose bodies are labeled sums. Each of the
carried types T11 , T12 , T21 can mention the explicitly quantified type αa , and the names
of each of the datatypes in the bundles (αt1 , αt2 ). Because of the syntactic enforcement
of uniformity, these types do not appear in application positions. After the declaration,
however, t1 and t2 are bound as lambdas that take αa as an argument and expand to
142
the µ. The IL does not have a type definition mechanism so this is accomplished using
the same mechanism we use for EL type abbreviations.
Each constructor is validly bound at the arrow type for injecting into the datatype;
this is so that they can be used as if regular functions. We additionally note that they
are constructors, however, so that we can pattern match against them and treat their
applications as valuable. These bindings are supported by IL declarations that declare
the constructor functions, using the modality and immediately eliminating it, as usual.
(I have elided the type annotations on the roll and inj here.)
Extensible types
Γ ` tagtype id 99Kw
D αid ::Ext | tagtype αid
The hypothesis xg taggerx is like the ctor hypothesis for datatype constructors. It
enables us to recognize the identifier xg as an extensible type constructor and associates
it with the tag value x used for matching against it.
We also have valid tags, which are a bit more interesting:
Here, after generating the tag we make it valid with put. (This requires that the
type carried by the tag is mobile, which we check.) We can then declare the construc-
tor function to be valid using the standard idiom of introducing the modality and
immediately eliminating it.
Case analysis on extensible types expands to iterated use of the untag construct in
143
the IL. For example, the rule for elaborating a case analysis on extensible types is
Γ ` E 99K M : α @ w
Γ(T) = taggerx
Γ, xid :x ` E1 99K N1 : C @ w
Γ ` E2 99K N2 : C @ w
case E of untag M with v of
Γ` T id ⇒ E1 99K yes ⇒ xid .N1 : C @ w
| ⇒ E2 | no ⇒ N2
Network signatures
The extern family of declarations are elaborated easily, since the IL has corresponding
constructs. For a value import:
Γ, αa1 ::Reg, . . . , αan ::Reg ` T 99KT A Γ ` W 99KW w
0
Γ ` extern val (a1 , . . . , an ) id : T @ W = id0 99Kw D
xid :∀αa1 , . . . , αan .A @ w | extern val xid :∀αa1 , . . . , αan .A @ w = id0
Because we bind a type variable, if the same type label is imported in multiple places
those bound type variables will not be equal. This is not desirable because these im-
ports are supposed to be definite references. There are a variety of ways to remedy this,
but the right one is to implement a real separate compilation system based on mod-
ules [130]. In the current implementation, the programmer can easily avoid this by only
importing a given type once, at the top of his program.
Finally, the extern world declaration informs us of the existence of the named
world constant. We record this fact of the identifier:
144
Pattern matching
Pattern matching in ML5/pgh accounts for about one third of the code implementing
elaboration, but very little of it is related to the modal features. Therefore, I will only
discuss it briefly.
The algorithm, which is based on the one used by TILT [134], works on a generaliza-
tion of case analysis to a matrix of rows and columns, i.e.,
case v1 : A1 . . . vn : An of
| p11 ... p1n ⇒ E1
.. ...
| . pmn ⇒ Em
| ⇒ Edef
The values v1 through vn are already elaborated, and we insist that they are values
(usually variables) so that we can duplicate or eliminate them as we compile the pattern.
Every EL pattern can be straightforwardly converted to one of these extended patterns.
There are then two mutually recursive phases of compilation: clean and reduce.
Clean. Cleaning the pattern means establishing an invariant about each of the patterns
in the matrix: At its outer level, a pattern must be an application pattern, a constant pat-
tern (integer or char) or a wildcard. Cleaning therefore eliminates n-tuples (by explod-
ing them into n new columns that match on the components of the tuple); variables and
as patterns (by binding the variable within the arm and replacing it with a wild pattern
or the as pattern); when patterns (by applying the when expression and matching on the
result); and type constraints (by unification). A clean matrix is then subject to one round
of reduction.
Reduce. Reducing a pattern makes the matrix smaller through a variety of transforma-
tions. For example, a column that consists of only wildcard patterns can be eliminated.
A column that consists of application patterns (extensible and datatype constructors), or
of constants, is compiled into a primitive case construct (a sumcase, a series of untags
or an intcase), each with a nested pattern match for the rest of the matrix. We take
some care to make sure that these nested patterns do not duplicate code, by hoisting out
common pattern matches as functions. Heuristics guide when to apply the reductions;
some cause less code duplication than others.
This algorithm does not lend itself well to exhaustiveness checking, so we use a dif-
ferent technique to issue warnings. A special effectless primop CompileWarn is gener-
ated in the IL code right before the compiler inserts the raise Match corresponding
to an inexhaustive pattern match. This primop contains the name of the source file and
the position from which it arose. As the code is compiled and unreachable branches
are eliminated, so are the warning markers. When we finally generate code, we emit
the warnings if they still exist. This gives particularly good warning messages, which
are also precise because they take into account any optimizations that the compiler per-
forms.
145
Type inference
Finally, the elaboration relation that I have described is highly nondeterministic, requir-
ing that types and worlds be guessed in order to form a derivation. Elaboration is actu-
ally implemented using a variant of Hindley-Milner type inference [27, 75]. We add to
the language of IL types a type metavariable X and to worlds a world metavariable O.
These are implemented as ML reference cells. During elaboration, we generate new
metavariables and perform unification to determine their true identities. For example,
consider a simplified rule for function application:
Γ ` E1 99K M1 : (A) → B @ w Γ ` E2 99K M2 : A @ w
Γ ` E1 E2 99K M1 (M2 ) : B @ w
(It is actually more complicated, because the application of a constructor to a value is a
value, etc.) To type-check, it requires us to guess some combination of the types A and
B and the world w. To perform type inference, we do something like the following:
elab (App(E1 , E2 )) =
let XB = new-evar() in
let M1 : A1 @ w1 = elab E1 in
let M2 : A2 @ w2 = elab E2 in
unify-type A1 ((A2 ) → XB ) in
unify-world w1 w2 in
(M1 M2 ) : XB @ w1
We know that whatever M1 ’s type is, it must be a function, so we unify it against
a function type. This allows us to name the return type, even if it has not yet been
determined. We also unify the worlds of the function and its argument; unification is
much simpler here because worlds are not structured.
The interesting part of type inference is the inference of polymorphism and validity.
To elaborate a declaration val x = E, we elaborate E at an existential world O, in
general yielding M : A @ O. If M is an expression, we must use the monomorphic IL
val rule, so we unify O with the current world and are done.
If M it is value v, we might make a polymorphic binding. For type polymorphism,
we inspect the type A to see if it has any metavariables in it whose identity has not yet
been determined (“free” metavariables). If these metavariables do not appear anywhere
in the elaboration context, then they will always be free because there will be no way
to unify with them again. We therefore instantiate each one with a new type variable α
and ∀-quantify over these variables.
World polymorphism is similar: We inspect A looking for world metavariables. If
any exist and are not bound in the context or are the metavariable O, then we instantiate
and ∀-quantify those as well.
The reason that we avoid the world O is that it is outside the scope of the ∀ quan-
tifiers, since it is part of the judgment, not the type. However, we have a way to gen-
eralize it as well. If O is still free and does not appear in the context, then we use the
polyletsham elaboration rule to produce a valid, world- and type-polymorphic bind-
ing. If O is determined or escapes into the context, then we use the polyleta elabo-
146
ration rule to produce a world- and type-polymorphic binding at that possibly remote
world.
At the end of elaboration, some metavariables may have not been determined. If
this is the case, we set type metavariables to unit and world metavariables to home.
One complication of type inference is that we have a distinguished class of types:
those types that are mobile. For example, if we elaborate the program
fun f y =
let put z = y
in 0
end
we need to know that the type of y is mobile, but we don’t know what it is. We handle
this by postponing the mobility check (if necessary) until the end of elaboration. In the
above program, the declaration of f cannot be polymorphically generalized, because
the metavariable will be considered “in the context” as it waits for the mobility check at
the end of elaboration.1 However, the program
fun g y =
let put z = hold y
in 0
end
does produce a polymorphic binding for g, because the type of hold y is X at w, which
is mobile for any X.
5.3.4 Optimization
After the program is elaborated, there is a simple optimization phase to eliminate dead
code. This can usually discard much of the standard libraries, which is good because
the CPS phases are much slower than the IL phase. Conversion to CPS is next; the CPS
language is therefore described in the following section.
Types. The types that have changed are as follows. Functions have been replaced by
continuations, which do not return. However, we still have a bundle of mutually recur-
sive continuations, written (A~1 , . . . , A~m ) conts. Each may take multiple arguments. We
1
If we had a kind of bounded quantifier over only mobile types, then we could generalize. Such a
construct is complicated and not well-motivated.
147
worlds w ::= ω | w
types A, B ::= α | {`1 : A1 , . . . , `n : An } | [`1 : A?1 , . . . , `n : A?n ]
~ cont | (A~1 , . . . , A~m ) conts
| A
| A ref | A array | A tag
| A at w | ω A | w addr
| πi (µ α0 .A0 , . . . αn .An )
| ∃α.A ~ | ∀h~ω ; α ~
~ ; Ai.B
| exn | bytes | A rep | w wrep
optional type A? ::= — | A
Figure 5.11: The continuation expressions of the ML5 CPS language. Again we use α
~ or
x : A to denote a sequence of arbitrary length.
148
vals v ::= x|u
| fns(f1 (x1 : A1 ) = c1 , . . . , fn (xn : An ) = cn )
| fsel v.n | n | "string" | #` v
| {{`1 = v1 , . . . , `n = vn }} | heldw v
| sham ω.v | injA ?
` v | rollA v | unroll v
| pack A, vd , ~v as ∃α.B ~
| unpack α; uα ; x : A = v in v 0
| ~ ; x:Ai.v | vf h~
Λh~ω ; α ~ ~v i
w; A;
| tag v with vt
| leta x = v in v 0 | letsham u = v in v 0
| wrepfor w | wrep w | repfor A
| rep {`1 : v1 , . . . , `n : vn } | rep [`1 : v1? , . . . , `n : vn? ]
| rep (~v cont) | rep ((v~1 , . . . , v~m ) conts)
| rep (v at v 0 ) | rep (ω,u) v
| ...
optional vals v ? ::= v | —
Figure 5.12: The values of the ML5 CPS language. For every syntactic form of type
there is a syntactic form of value, its representation. I do not repeat all of these here.
now have a single extensible type, called exn; all extensible types in the IL are trans-
lated to this one. The type of marshaled data is bytes. For closure conversion, we have
an existential type. We no longer have a prenex restriction on polymorphism, so we
have a first-class polymorphism construct. This ∀ type quantifies over a series of world,
type, and value variables all at once.
We also have a type of run-time world representations, and a type of type represen-
tations. A value of type A rep is a representation of the type A; it is a singleton type.
Similarly, a value of type w wrep is a representation of the world w.
Expressions. The syntax for CPS expressions appears in Figure 5.11 and their typing
rules in Figure 5.13. As usual, continuation expressions are typed with a judgment
Γ ` c?w
which means that c is well-typed to evaluate at the world w. Some constructs (go, say,
and extern type) have several versions (which have different typing rules), only one
of which will be in use depending on what phase of the compiler we are in. The bare go
construct takes an address and a continuation for us to execute at the remote world; it
does not return. The go cc construct is the same thing post closure-conversion; it takes
a closure to execute remotely. The go mar construct takes a marshaled closure. We can
marshal any value with marshal, as long as we have a representation of its type. (Un-
marshaling is implicit in the go mar construct.) For say, we have a typing rule similar
149
to the IL one. After closure conversion, say cc expects a closure-converted continu-
ation, which unfortunately means that the type of a closure-converted continuation is
baked into the rule.2
The expression primcall represents a call to a function imported with the IL
extern val construct. Such functions cannot be CPS converted, so we need a spe-
cial way of making direct-style calls to them. The native expression is a single primop
applied to values; primops include mathematical operations on integers, comparisons,
and access to arrays and references. (There are a variety of typing rules, all obvious,
so I do not give them here.) As in the IL, extern world has no effect on the typing
context. The extern type expression may additionally declare the label for the type’s
representation. These are inserted in the first type representation phase (Section 5.4.5).
The newtag and untag expressions are as in the IL except that they now use the single
universal type exn.
Values. As expected, the typing judgment for values (given in Figure 5.14) is
Γ ` v : A@w
meaning that v has the type A at the world w. It may be surprising, however, what we
consider a value in the CPS language. It includes, for instance, leta and letsham—which
bind variables—and other elimination forms such as the application of a Λ to types,
worlds, and values. These turn out to be necessary because as we compile the language,
things that were once simple values (e.g. functions) become more complicated. Because
the language has several value restrictions (for the body of held and sham, for example),
we must maintain valueness through these translations. Perhaps a better terminology
would be to call continuation expressions “statements” and values “pure expressions.”
This is because the value restriction is not really a restriction to values but a restriction
to non-effectful expressions. Still, this is the terminology we use elsewhere, so we will
stick with it here.
We have the usual constructs: variables, continuation bundles, selection from such
bundles, integer and string constants, records and projection from them, etc. We have
a value pack for creating existential types. In addition to the type, this requires a rep-
resentation of the type. This is so that when we unpack the existential, binding a type
variable, we will be able to maintain the invariant that every type variable in scope has
a representation also in scope. (The representation must be valid, which we encode by
using the modality.) The existential package contains a series of values, which eases
closure conversion. Unpacking, which is also a value, binds a type variable and a valid
variable to its representation, as well as a variable for each of the packed values.
The value of ∀ type is a lambda over a sequence of world, type, and value variables.
Its body must be a value, because the application of such a value to actual world, types,
and values is also considered a value. The order of the arguments is important, in
2
It would be nicer to make the say construct a primop or feature of the runtime system that we
simply import. Unfortunately the code generator must know about it because of problems with the way
that events work in JavaScript.
150
Γ ` va : w0 addr @ w
Γ ` vf : (A1 , . . . , An ) cont @ w Γ ` ve : A @ w 0
Γ ` v1 : A1 @ w · · · Γ ` vn : An @ w Γ ` vf : A cont @ w0
Γ ` call vf (v1 , . . . , vn ) ? w Γ ` go cc[w0 , va , ve , vf ] ? w
Γ ` c ? w0 Γ ` va : w0 addr @ w Γ ` va : w0 addr @ w Γ ` vm : bytes @ w
Γ ` go[w0 , va ] c ? w Γ ` go mar[w0 , va , vm ] ? w
Γ ` v : A @ w Γ ` vr : A rep @ w Γ, x:bytes @ w ` c ? w Γ, x:A tag @ w ` c ? w
Γ ` let x = marshal(v, vr ) in c ? w Γ ` newtag x of A in c ? w
Γ ` vo : exn @ w Γ ` vt : A tag @ w Γ, x:A @ w ` c ? w Γ ` c0 ? w
Γ ` untag vo with vt of yes ⇒ x.c | no ⇒ c0 ? w
Γ ` v1 : A1 @ w · · · Γ ` vn : An @ w Γ, x:B @ w ` c ? w
Γ ` let x = primcall(` : (A1 , . . . , An ) → B)(v1 , . . . , vn ) in c ? w
Γ ` v : ∃α.A1 , . . . , An @ w Γ, α type, uα ∼α rep, x1 :A1 @ w, . . . , xn :An @ w ` c ? w
Γ ` unpack α; uα ; x1 :A1 , . . . , xn :An = v in c ? w
Γ, x:A @ w0 ` c ? w Γ, u∼ω.A ` c ? w
Γ ` extern val x : A @ w0 = ` in c ? w Γ ` extern val u ∼ ω.A = ` in c ? w
Figure 5.13: The typing rules for ML5 CPS continuation expressions. I omit the typing
rules for put, letsham, leta, val, intcase and sumcase because they are essen-
tially the same as they were in the IL.
151
Γ(x) = A @ w Γ(u) = ω.A
Γ ` x : A@w Γ ` u : [ w/ω ]A @ w
Γ0 , x11 :A11 @ w, . . . x1m :A1m @ w ` c1 ? w
..
.
Γ0 , xn1 :An1 @ w, . . . xnk :Ank @ w ` cn ? w
Γ0 = Γ, f1 :(A11 , . . . , A1m ) cont @ w, . . . , fn :(An1 , . . . , Ank ) cont
f1 (x11 : A11 , . . . , x1m : A1m ) = c1 , (A11 , . . . , A1m ),
Γ ` fns( .
. ):( .. ) conts @ w
. .
fn (xn1 : An1 , . . . , xnk : Ank ) = cn (An1 , . . . , Ank )
0 ≤ i ≤ n Γ ` v : ((A01 , . . . , A0m ), . . . , (An1 , . . . , Ank )) conts
Γ ` fsel v.i : (Ai1 , . . . , Aim ) cont @ w
Γ ` v : A @ w0 Γ, ω world ` v : A @ ω Γ ` v : A @ w Γ ` vt : A tag @ w
Γ ` heldw0 v : A at w0 @ w Γ ` sham ω.v : ω A @ w Γ ` tag v with vt : exn @ w
Γ ` vd : (A rep) @ w Γ ` v1 : [ A/α ]B1 @ w · · · Γ ` vn : [ A/α ]Bn @ w
Γ ` pack A, vd , v1 , . . . , vn as ∃α.(B1 , . . . , Bn ) @ w
Γ, ω1 world, . . . , ωn world, α1 type, . . . , αm type, x1 :A1 @ w, . . . , xk :Ak @ w ` v : B @ w
Γ ` Λhω1 , . . . , ωn ; α1 , . . . , αm ; x1 :A1 , . . . , xk :Ak i.v
: ∀hω1 , . . . , ωn ; α1 , . . . , αm ; A1 , . . . , Ak i.B @ w
Γ ` vf : ∀hω1 , . . . , ωn ; α1 , . . . , αm ; A1 , . . . , Ak i.B @ w
Γ, ω1 world, . . . , ωn world, α1 type, . . . , αm type ` v1 : A1 @ w
..
.
Γ, ω1 world, . . . , ωn world, α1 type, . . . , αm type ` vk : Al @ w
Γ ` vf hw1 , . . . , wn ; A1 , . . . , Am ; v1 , . . . , vk i : [ w1 ,··· ,wn/ω1 ,··· ,wn ][ A1 ,··· ,Am/α1 ,··· ,αm ]B @ w
Figure 5.14: The typing rules for ML5 CPS values. I omit the typing rules for integers,
strings, records and record projection, sum injections and case analysis, and roll and
unroll for inductive types because they are essentially the same as they were in the
IL. I also omit the value versions of unpack, leta, and letsham because they are
essentially the same as their expression counterparts. Some of the rules for type and
world representation values appear in Figure 5.15.
152
Γ ` wrepfor w0 : w0 wrep @ w Γ ` wrep w : w wrep @ w Γ ` repfor A : A rep @ w
Γ ` v : A rep @ w
Γ ` rep (v ref) : (A ref) rep @ w Γ ` rep exn : exn rep @ w
Γ ` v1 : A1 rep @ w · · · Γ ` vn : An rep @ w
Γ ` rep (v1 , . . . , vn cont) : (A1 , . . . , An ) cont rep @ w
Γ ` v1 : A1 rep @ w · · · Γ ` vn : An rep @ w
Γ ` rep {`1 : v1 , . . . , `n : vn } : {`1 : A1 , . . . , `n : An } rep @ w
vi? = — when A?i = —
Γ ` vi? : A?i rep @ w when A?i 6= —
Γ ` rep [`1 : v1? , . . . , `n : vn? ] : [`1 : A?1 , . . . , `n : A?n ] rep @ w
Γ ` v : A rep @ w Γ ` v 0 : w0 wrep @ w
Γ ` rep (v at v 0 ) : (A at w0 ) rep @ w
Γ, ω world, u∼ω wrep ` v : A rep @ w Γ ` v : w0 wrep @ w
Γ ` rep ω,u v : ( ω A) rep @ w Γ ` rep (v addr) : (w0 addr) rep @ w
Γ0 = Γ, α0 type, uα0 ∼α0 rep, . . . , αn type, uαn ∼αn rep
Γ0 ` v0 : A0 rep @ w
..
.
Γ0 ` vn : An rep @ w
Γ ` rep πi (µ (α0 , uα0 ).v0 , . . . (αn , uαn ).vn ) : πi (µ α0 .A0 , . . . αn .An ) rep @ w
Γ, α type, uα ∼α rep ` v1 : A1 rep @ w · · · Γ, α type, uα ∼α rep ` v1 : A1 rep @ w
Γ ` rep (∃(α, uα ).v1 , . . . , vn ) : (∃α.A1 , . . . , An ) rep @ w
Γ, ω1 world, uω1 ∼ω1 wrep, . . . , α1 type, uα1 ∼α1 wrep ` v1 : A1 rep @ w
..
.
Γ, ω1 world, uω1 ∼ω1 wrep, . . . , α1 type, uα1 ∼α1 wrep ` vn : An rep @ w
Γ, ω1 world, uω1 ∼ω1 wrep, . . . , α1 type, uα1 ∼α1 wrep ` v : B rep @ w
Γ ` rep (∀h(ω1 , uω1 ), . . . ; (α, uα1 ), . . . ; v1 , . . .i.v) : (∀hω1 , . . . ; α1 , . . . ; A1 , . . .i.B) rep @ w
Γ ` v : A rep @ w Γ ` v : w0 wrep @ w
Γ ` rep (v rep) : (A rep) rep @ w Γ ` rep (v wrep) : (w0 wrep) rep @ w
Figure 5.15: The typing rules for ML5 CPS world and type representations, which are
values. I omit the straightforward rules for bytes, A array, A tag and conts.
153
that the types of the value arguments may depend on the type arguments and world
arguments.
Finally we have world and type representations, whose typing rules appear in Fig-
ure 5.15 (though they are values like any other). World representations are simpler.
There are two: wrepfor w is a stand-in for the representation of the world w. We use
this form until we have established an invariant where every world variable ω has asso-
ciated with it a valid variable of type ω wrep, and then replace wrepfor ω with that valid
variable (Section 5.4.7). A “real” value of type w wrep is wrep w for a concrete world w.
These are the representations that are actually passed around at runtime. The stand-in
repfor A is similar to wrepfor. There is one syntactic form of rep for each syntactic
form of CPS type (except variables). However, where there were type components in
the type, there are now value components in the representation. For example, the record
type
{`1 : exn, `2 : α ref}
is represented by the value
if uα has type α rep. Moreover, those syntactic forms of type that bind type or world
variables (such as ω A) must now bind two variables: the type or world variable, and
the valid variable standing for its representation. For example, the representation of
ω (exn at ω)
is
rep ( (ω,u) rep((rep exn) at u))
where u∼ω wrep is bound within the value’s body.
This syntactic redundancy is somewhat annoying, because in the implementation
each type constructor must be given twice (as a type and as a value), and any utility code
(for pretty-printing, etc.) must also be written twice. Fortunately, the CPS representa-
tion that we use in ML5/pgh has a nice interface that removes some of this redundancy.
Before we begin describing the translation from IL to CPS and the steps of compilation
that take place in the CPS representation, let us discuss its ML implementation.
5.4.1 Return to Oz
In this section I discuss the ML5/pgh implementation of the CPS abstract syntax. Al-
though I think this is a nice technique, it is not a main contribution of the dissertation
and not necessary for understanding the remainder of this chapter, and so may be safely
skipped.
The standard way of representing an abstract syntax tree in ML is via its algebraic
datatype mechanism. For example, a straightforward encoding of the ML5 CPS types
in Standard ML would be
154
datatype ctyp =
At of ctyp * world
| Cont of ctyp list
| Conts of ctyp list list
| Shamrock of var * ctyp
| Mu of int * (var * ctyp) list
| (* ... *)
This is a very convenient implementation, particularly because of our ability to write
functions over datatypes via pattern matching. However, it has a shortcoming, which
is that we do not have any control over the representation of the datatype. This can
be a problem when we want to do something special with the representation, such as
use a more efficient data structure, store additional information, or maintain certain
invariants. In these cases what we want from the datatype mechanism is its facility for
pattern matching, not its automatic creation of constructors and destructors from the
representation it chooses. Unfortunately, ML offers no direct way to decouple the two.3
In my undergraduate senior thesis I studied a technique for alternative implemen-
tations of datatypes while retaining a form of pattern matching, known as the “Wiz-
ard” [84]. The idea is to provide an abstract type (which can be implemented any way
we want) and a projection function that produces a single-level datatype that we can
pattern match on. For example,
type ctyp (* abstract *)
datatype ctypfront =
At of ctyp * world
| Cont of ctyp list
| Conts of ctyp list list
| Shamrock of var * ctyp
| Mu of int * (var * ctyp) list
| (* ... *)
val look : ctyp -> ctypfront
val hide : ctypfront -> ctyp
The important thing is that the datatype is only partially revealed; it refers to the
abstract type rather than being recursive. To create an element of the abstract type,
we repeatedly apply datatype constructors and the hide injection. When we want to
pattern match on a ctyp, we simply call the projection function on the case object:
case look t of
At (t, w) => (* ... *)
| Cont l => (* ... *)
| _ => (* ... *)
The biggest drawback is that we can only pattern match one level at a time, because
there is no place to put the projection function in a nested pattern match. I found such
patterns to be very rare in practice. A refinement of this representation is to make the
3
Because Standard ML uses the opaque interpretation of datatypes [137], it would not be hard for a
future version to support such a mechanism, however.
155
partially exposed datatype parameterized by a type:
type ctyp
datatype ’a front =
At of ’a * world
| Cont of ’a list
| Conts of ’a list list
| Shamrock of var * ’a
| Mu of int * (var * ’a) list
| (* ... *)
val look : ctyp -> ctyp front
val look2 : ctyp -> ctyp front front
(* ... *)
This allows us to provide a projection function that reveals the abstract type to two
or more levels, for nested pattern matches. Another useful trick is to provide injective
constructors
val At’ : ctyp * world -> ctyp
val Cont’ : ctyp list -> ctyp
(* ... *)
so that we can build elements of the abstract type as if it were a datatype.
The purpose of the original project was to improve the performance of TILT by using
a more efficient representation (de Bruijn indices and hash consing) without rewriting
the code. Although it was feasible to retrofit a new representation into the compiler
with this technique, the performance results were negative. The code was substan-
tially slower when running through the Wizard interface, and—disappointingly—even
slower when the representation “optimizations” were turned on.
The ML5/pgh compiler uses a Wizard-like interface for the CPS language, but not
for efficiency (indeed, it comes at a performance cost here as well). Instead, we use
it to improve the correctness of the code, by providing an interface to the CPS lan-
guage that helps to avoid alpha-conversion bugs. (Various subtle alpha-conversion
mistakes were the predominant source of bugs in the Humlock compiler’s CPS lan-
guage [91] with hand-managed binding.) We do this by making the CPS representation
aware of the binding structure of the AST. We provide primitive substitution, renaming,
free variable computations and comparisons. Additionally, whenever we look under a
binder, we see a new freshly alpha-varied variable that has never been seen before.
Programming against this interface thus resembles programming with higher-order ab-
stract syntax (Section 4.7.1) or other high-level representations of binding structure like
FreshML’s [110].
Both the interface to the CPS language and its implementation are interesting; let us
first look at the interface.
156
CPS interface
The CPS interface looks like the final code example above, with one further trick em-
ployed. Because we parameterized the datatype for type fronts, it can equally well be
used for the implementation of types and for the implementation of type representation
values. It looks like this:
datatype (’tbind, ’ctyp, ’wbind, ’world) ctypfront =
At of ’ctyp * ’world
| Cont of ’ctyp list
| TExists of ’tbind * ’ctyp list
| Shamrock of ’wbind * ’ctyp
| (* ... *)
type ctyp
val ctyp : ctyp -> (var, ctyp, var, world) ctypfront
type cval
type cexp
val cval : cval -> (cexp, cval) cvalfront
val cexp : cexp -> (cexp, cval) cexpfront
Here the typfront type is parameterized over its recursive occurrence 0 ctyp but also
the type of type bindings, world bindings, and worlds. When we project from the ab-
stract ctyp type, we get a ctypfront where the binders are single variables and types are
ctyps and worlds are worlds, as expected. To reuse the datatype (which is much longer
than shown here) for type representations, we instead instantiate 0 ctyp and 0 world with
the type of values, because the representations recursively contain more representa-
tions, not types. Binders are instantiated with pairs of variables: one for the static type
or world and one for its representation. This means that a lot of utility code (for exam-
ple, pretty printing) can be implemented once in a generic way for both types and their
representations.
In the interface we also have injective constructors for each arm of the datatypes,
functions for computing free variable sets, substitutions, alpha-equivalence respecting
total orders, etc. These have the straightforward types.
Again, the nice thing about this interface is that it automatically renames bound vari-
ables as we open their binders. This means that every binding we see is globally unique,
157
so we do not have to worry about name clashes as we do transformations of the code.
This simplifies the client’s job. Such a representation also discourages the programmer
from doing wrong or suspicious things. For example, a naı̈ve program might make a
pass over the code to find functions that ought to be inlined, identifying those func-
tions by the variable they are bound to. Making a second pass and expecting to find
the functions bound to the same variables is ideologically suspect—names should not
matter!—and might even be wrong, if the process of inlining can cause these variables
to alpha-vary because of substitution.
CPS implementation
The first implementation of the CPS language was done by hand, writing the renaming
and substitution functions over the natural datatype representation. The language is
fairly large, so this turned out to be error-prone and difficult to experiment with. This is
frustrating because the algorithms are repetitive and structured: each binder or variable
occurrence is treated exactly the same way. To avoid writing repetitive code, I reimple-
mented the CPS language using a second Wizard. The second language, called “AST”,
is a tiny kernel language with constants, aggregation, binding structure, and nothing
else. The language is similar to an untyped LF or Harper’s Abstract Binding Trees [49].
The AST language is functorized over the type of variables and leaves:
signature ASTARG =
sig
type var
val var_cmp : var * var -> order
val var_vary : var -> var
type leaf
val leaf_cmp : leaf * leaf -> order
end
For variables, we only need to know how to order them (and therefore test for equal-
ity) and to alpha-vary them. For leaves, which can be anything, we only need to know
how compare them. Given these types, the interface to the AST is
type ast
datatype ’ast front =
$ of leaf
| / of ’ast * ’ast
| \ of var * ’ast
| V of var
158
val $$ : leaf -> ast
val // : ast * ast -> ast
val \\ : var * ast -> ast
val VV : var -> ast
In this language, we can have a leaf, a pair of two ASTs, a variable bound within
an AST or a variable occurrence. (The actual implementation also supports a list of
ASTs and a list of binders, since these are quite common. I will ignore them for this
discussion.) We also get functions for comparing ASTs, substituting for variables, and
computing the free variables of an AST. Each time we look under the binder \, the
variable is renamed to a freshly generated variable that we have never seen before.
The nice thing about this language is that it is quite minimal, so it is possible to
easily experiment with different implementations of it. I have developed a few different
implementations; the most complex one uses a limited form of explicit substitution to
avoid the cost of renaming variables in certain common scenarios. A conformance suite
tests that it has the correct behavior.
We use the AST functor to build the implementation of the CPS language. We start
by declaring a leaf datatype that has all of the constructor names for our language;
these do not carry any values. (For example, there is a constructor AT which we use to
encode the At of ctyp ∗ world constructor.) We must define a comparison functions on
leaves as well. The type of variables is instantiated with a disjoint union of four sorts of
variables:
datatype var =
WV of [Link] (* world variable *)
| TV of [Link] (* type variable *)
| MV of [Link] (* modal variable *)
| VV of [Link] (* valid variable *)
Then, we can implement the CPS language by making each of the abstract types world,
ctyp, cval, and cexp be the ast type. The injective constructors encode the CPS lan-
guage elements into ASTs. For example:
fun At’(t, w) = $$AT_ // t // w
fun Shamrock’(wv, t) = $$SHAMROCK_ // (WV wv \\ t)
Recall that $$, // and \\ (the latter two used with infix notation) are the injective
constructors for the AST, where \\ is the binder. To project out an abstract CPS world,
type, value, or expression, we project out the AST term that implements it (usually
several levels) and pattern match:
fun ctyp (typ : ast) =
case look2 typ of
$AT_ / t / w => At(t, w)
| $SHAMROCK_ / (WV v \ t) => Shamrock(v, t)
| (* ... *)
| _ => raise CPS "bad ctyp"
Note that we have some costs here. For one, we perform many more run-time tag
checks to distinguish the different cases during these pattern matches. Some of these tag
159
checks result from a loss of static information about what forms a value might take. Each
of the syntactic classes is represented uniformly as an AST, and the type system does not
tell us that the AT leaf is always followed by a type and a world, for example.4 These
runtime costs are constants, however, and the loss in static checking easy to manage.
(Because we create ASTs in a structured way, our destructing pattern matches either
work every time or fail immediately.)
As benefits, we get correct substitution functions for all of the constructs of the lan-
guage, free variable computations, and alpha-conversion respecting comparison func-
tions. Comparisons are used on types during type-checking, naturally, but having a
for-free comparison function for expressions and values was quite useful during opti-
mizations (for example, in the hoisting phase we are able to conveniently coalesce code
that is identical up to alpha-conversion).
The implementation of the CPS language in terms of AST is only about twice as long
as its signature (which contains mainly the datatype declarations), so it is not much
code at all. Essentially, it consists of two (injection and projection) explications of the
binding structure of the language described by the constructs of AST. Other than this
duplication, it is about as terse and direct as could be.
Double-barreled CPS
We translate away exceptions during CPS conversion by using “double-barreled CPS.”
The double barrels are the two continuations passed to each function: the return contin-
uation, used for returning normally, and the exception continuation, used for throwing
an exception.
CPS conversion of expressions is given almost as before:
convert Γ K M A w
where M is the expression to convert, A and w are its IL type and world. The context Γ
is a CPS typing context, and K is a meta-level function indicating how to form the tail
of the expression from the new context and CPS value. (As before, [[A]]C converts an IL
@w
type to a CPS type and [[v]]A
VC converts values.) To implement double-barreled CPS,
we maintain an additional invariant that Γ contains a modal variable handler with type
exn cont at the current world w. Initially, this continuation simply halts the program.
The CPS conversion of the IL raise M expression is the simplest:
4
A more clever use of the AST would be able to regain some amount of static checking through the
use of phantom types, but this code is so simple as to make this overkill.
160
[(A ,...,A )→B,...] @ w
[[fns(f1 (x11 , ldots) = M1 , . . .)]]VC 11 1n
=
fns(f1 (x11 , . . . , xr :[[B]]C cont, handler :exn cont) = c1 , . . .)
where c1 =
convert (Γ, x11 , . . . , xr :[[B]]C cont)K M1 B w
where K(Γ; v) = call xr (v)
convert Γ K (raiseA M ) A w =
convert Γ K0 M exn w
where K0 (Γ; v) = call handler(v)
We first CPS-convert M , which must be of type exn. When we have its value, v, we
simply call the current handler on v. Maintaining this handler is the job of the rest of
the translation. For example, the IL handle expression is converted as follows:
convert Γ K (M handle x.N ) A w =
val xj = fn(xr ).K(Γ, xr :[[A]]C ; xr ) in
val handler = (fn(x).
convert (Γ, x:exn @ w) K0 N A w
where K0 (Γ; v) = call xj (v)) in
convert (Γ, xj :exn cont) K00 M A w
where K00 (Γ; v) = call xj (v)
The function xj is the join point for the two possible ways of returning from this
expression: either directly or via the handler. (fn (~x).c is shorthand for a bundle con-
taining a single continuation, which is selected out.) It is simply the reification of the
input continuation K. We then shadow the continuation variable handler with the new
handler. It executes N (with the old handler in scope) and returns to the join point. With
this defined, we convert M , which might call our new handler or return normally to the
join point.
161
To maintain the handler dynamically through the call stack, we pass it as an addi-
tional argument to every function along with the return continuation (Figure 5.16). The
interesting part of the implementation of exceptions is their interaction with get. Recall
that get is translated into two gos; one to go to the remote world and one to return. To
implement exceptions, we need to make sure that we establish a handler at the remote
world that re-raises the exception at the source world. Let’s assume that get takes an
address as a value va to shorten the presentation; then its translation is
convert Γ K (get[w0 , va ] N ) A w =
val xold = handler in
val xr = localhost() in
put ur = xr in
go[w0 ; va ]
val handler = (fn (xe ).
put ue = xe in
go[w; ur ]
call xold (ur )) in
convert (Γ, xr :w addr @ w, ur ∼w addr, xold :exn cont @ w) K’ N A w’
where K0 (Γ, v) = put u = v in
go[w; ur ]
val handler = xold in
K(Γ, u∼[[A]]C ; u)
The idea is as follows. We save the current handler (at the source world w) in the
variable xold . When we arrive at the destination, we immediately bind a new handler for
the time that we are executing there. It takes a value of type exn and binds it to a valid
variable with put—this requires that the extensible type exn be mobile (Section 5.1.4).
We then travel back to the source world and call the original handler xold on the exception
value. If we return from the get normally, we restore the handler to its original value.
First-class continuations
First-class continuations are also translated away during the conversion to CPS—in fact,
this is one of the reasons we use CPS, since there is no analog to letcc and throw in one
of our target languages, JavaScript.
The translation is isolated to the two constructs, and standard:
convert Γ K (letccA x in M ) A w =
val x = fn(xr ).K(Γ, xr :[[A]]C ; xr ) in
convert (Γ, x:[[A]]C cont @ w) K0 M A w =
where K(Γ, v) = call x(v)
For letcc, we reify the current continuation K, which M might throw to. If it does
not, we throw the result of M to it ourselves.
convert Γ K (throwA M to N ) A w =
convert Γ K0 M B w =
where K0 (Γ, v) = convert Γ K00 N (B cont) w
162
where K00 (Γ, vf ) = call vf (v)
Polymorphism
Polymorphism is treated simply by using the ∀ quantifier of the CPS language, which
quantifies over worlds, types, and values. In the IL all variables are fully applied to type
and world arguments, so their conversion is as follows
[ w1 ,...,wm/
ω1 ,...,ωm][ A1 ,...,An/α1 ,...,αn ]B @ w
[[xhA1 , . . . , An ; w1 , . . . , wm i]]VC
=
xh[[A1 ]]C , . . . , [[An ]]C ; w1 , . . . , wm ; ·i
if there are no type or world arguments, then we just generate the bare variable, not an
empty application. We do the same translation for valid variables.
Polymorphic variables are bound by various declarations. Conversion of IL declara-
tions is defined by the function
convertd Γ K D w
which converts D at the world w. The continuation K takes only the new context, since
there is no value returned by a declaration.
We must do some reorganization of the polymorphic bindings so that the types work
out correctly. For example, the polyleta construct must bind a variable at another
world with polymorphic type. We translate it as
convertd Γ K (polyleta (α1 , . . . , αn ; ω1 , . . . , ωm ) x = v) w =
leta x = heldw0 (Λhω1 , . . . , ωn ; α1 , . . . , αn ; ·i.
0
leta x0 = [[v]]VCat w @ w in
x0 ) in
K(Γ; x:∀hω1 , . . . , ωn ; α1 , . . . , αn ; ·iB @ w0 )
Here is our first use of the value form for leta, used to expand a value of type
A at w0 @ w0 to a value of type A @ w0 in place. Since the object of a polyleta is usu-
ally an immediate held, this often creates a useless eta-expansion of the value. These
are reduced in an optimization phase later.
The polyletsham binding is similar:
convertd Γ K (polyletsham (α1 , . . . , αn ; ω1 , . . . , ωm ) u = v) w =
letsham x = sham ω.(Λhω1 , . . . , ωn ; α1 , . . . , αn ; ·i.
letsham u0 = [[v]]VCω @ ω in
u0 ) in
K(Γ; u∼ω.∀hω1 , . . . , ωn ; α1 , . . . , αn ; ·iB)
163
Network signatures
The network signature declarations (extern) are straightforward in the case of type
and world imports. These are translated directly to their counterparts in CPS. The
extern val declaration also has a counterpart in the CPS, but we interpret it a different
way. The reason is that when we import a function, we need to use a direct-style calling
convention to call it, because we cannot CPS convert external functions. Therefore, the
translation of an extern val declaration differs based on the type of the imported value.
If A is a base type. . .
convertd Γ K (extern val x : ∀α1 . . . αn ∀ω1 . . . ωm .A @ w0 = `) w =
extern val x : ∀hα1 , . . . , αn ; ω1 . . . , ωm ; ·i.[[A]]C @ w0 = ` in
K(Γ; x:∀hα1 , . . . , αn ; ω1 . . . , ωm ; ·i.[[A]]C @ w0 )
If the value is imported at base type, then we just use the extern val of the CPS
language, which expects base types.
If A is {1 : A1 , . . . , k : Ak } → B where Ai and B are base types . . .
convertd Γ K (extern val x : ∀α1 . . . αn ∀ω1 . . . ωm .A @ w0 = `) w =
leta x = heldw0
(Λhα1 . . . αn ; ω1 . . . ωm ; ·i.
(fn(y : {1 : [[A1 ]]C , . . . , k : [[Ak ]]C }, xr : [[B]]C cont, handler : exn cont).
let z = primcall(` : ([[A1 ]]C , . . . , [[An ]]C ) → [[B]]C )(#1 y, . . . , #k y) in
xr (z))) in
K(Γ; x:∀hα1 , . . . , αn ; ω1 . . . , ωm ; ·i.({1 : [[A1 ]]C , . . . , k : [[Ak ]]C },
[[B]]C cont,
exn cont) cont @ w0 )
If it is a function taking a record as an argument, we treat this as a function taking
k arguments. We wrap a direct-style call to it using primcall in a continuation that
uses the CPS calling convention. We do a similar thing for imported valid values and
functions.
We also have a case for translating single-argument functions (so that they do not
need to be imported at the unitary record type, which is awkward). Additionally, if
the function returns unit, then we return a freshly created empty record rather than
use the return value of the primcall. This is because imported JavaScript functions
whose natural return type is unit don’t actually return an empty record, they return
“undefined.”
Summary
The rest of the conversion to CPS is similar to what we have discussed or otherwise not
interesting. Once in the CPS language, we immediately type-check the program to catch
compiler bugs, and then engage in a series of transformations from the CPS language
to itself. These transformations are all implemented using a functor for the convenient
definition of type-directed transformations, which is the subject of the next section.
164
5.4.3 Type-directed translations
This section describes another programming idiom for implementing type directed
compilers in Standard ML. It is independent from the previous one (Section 5.4.1) and
not necessary for understanding the remainder of the dissertation, so it may safely be
skipped.
Pointwise transformations
Very frequently, a transformation on the programming language’s syntax or types is
defined in a “pointwise” fashion. For example, the translation of types during closure
conversion is defined as
that is, we do a translation for the cont type but all of the other type constructors are
handled in a uniform way, simply applying the transformation recursively and recon-
structing the type. Similarly, closure conversion only needs to touch a few syntactic
forms (functions, calls to functions, etc.) and works in a similar pointwise fashion for
the others.
A nice way to implement these syntactic functions is to provide a single generic
pointwise transformation. This allows the individual transformations to be given by
only specifying the relevant cases. For example, the pointwise function on CPS types is
fun pointwiset (fw : world -> world) (ft : ctyp -> ctyp) typ =
case ctyp typ of
At (t, w) = At’(ft t, fw w)
| Cont l = Cont’ (map ft l)
| Shamrock (wv, t) = Shamrock’ (wv, ft t)
| (* ... *)
It takes a function to apply to worlds, and a function to apply to types, and the type
to transform. Pointwise it is not recursive; it simply applies the functions to each of
the constituent parts of this level of the datatype. The implementation of a specific
pointwise transformation is then just
fun cct typ =
case ctyp typ of
Cont l =>
let val a = newvar ()
in TExists’ (a, [a, Cont’ (map cct l @ [a, Cont’ [a]])])
end
| _ => pointwiset I cct typ
165
where newvar creates a new variable and I is the identity function (on worlds). The
idea is that at each level of the datatype, our conversion function is run to see if it
applies; if not, it appeals back to pointwise to apply itself to all of the constituent parts
of the constructor. Programming this way, a transformation needs only contain code for
the constructs that are relevant to what it is doing. This results in shorter programs and
it makes transformations work even if irrelevant constructs are added to or removed
from the object language.
In ML5/pgh we have a family of pointwise functions for IL and CPS types, values,
and expressions. They are used in a few places in ML5/pgh to briefly define purely
syntactic transformations. However, because the CPS language is typed, and has type
annotations within the code so that it can be effectively type-checked, the pointwise
family turns out to be inadequate for most of the passes in the CPS phase. The Pass
functor is a generalization to type-directed transformations that is powerful enough for
us to write all of the CPS passes in the compiler.
type selves = { selfv : arg -> context -> cval -> cval * ctyp,
selfe : arg -> context -> cexp -> cexp,
selft : arg -> context -> ctyp -> ctyp }
(* types *)
val case_At : arg -> selves * context -> ctyp * world -> ctyp
val case_Cont : arg -> selves * context -> ctyp list -> ctyp
(* ... *)
(* exps *)
val case_Go : arg -> selves * context ->
world * cval * cexp -> cexp
(* ... *)
166
(* vals *)
val case_Proj : arg -> selves * context ->
string * cval -> cval * ctyp
(* ... *)
end
The type arg is arbitrary, and chosen by the implementor. We can use it to pass
along contextual information needed by the transformation. Each case receives an arg.
It also receives a record containing the functions that should be used for recursive calls:
one each for values, expressions, and types. Then it receives the current typing context.
Finally, it has as arguments the body of the corresponding datatype constructor; the
case for At(t,w) gets a type and a world.
Each case should return the translated construct. For types and expressions, this
means returning a type or expression; for a value, we also must return its type.
From an argument module with signature PASSARG, the Pass functor produces a
structure that performs the complete transformation:
signature PASS =
sig
type arg
val convertv : arg -> context -> cval -> cval * ctyp
val converte : arg -> context -> cexp -> cexp
val convertt : arg -> context -> ctyp -> ctyp
end
The implementation of the Pass functor is very simple; it just pattern matches on
the construct and then “ties the knot” by passing the recursive function as the selves
argument to the appropriate case:
fun convertv z G va =
let val s = { selfv = convertv,
selfe = converte,
selft = convertt }
in
case cval va of
Lams a => case_Lams z (s, G) a
| Fsel a => case_Fsel z (s, G) a
| (* ... *)
end
and converte z G ex = (* ... *)
The trick is then as follows. Since we have exploded the case analysis into a collec-
tion of independent functions and used open recursion to recurse between them, we can
now program in a style that allows us to override the behavior for a set of constructs
that we choose. This is like inheritance in Object Oriented Programming languages.
We start with a PASSARG that implements the identity transformation. It is actually
a functor that takes an arg type:
167
functor IDPass(type arg) : PASSARG =
struct
(* ... *)
end
For each construct it does the necessary work to maintain the typing context
([Link] switches the current world, so that we’ll always know where we are
translating). It makes recursive calls through the self arguments.
Finally, to create a pass that does something interesting, we start from the identity
pass and override the cases for the constructs we are interested in. For example, here is
a simple optimization pass that reduces projections from literal records.
structure Reduce : PASSARG =
struct
structure ID = IDPass(type arg = unit)
open ID
end
This is the entire implementation. The utility function find searches for the label
in the record; if it is not found, the program is ill-formed. If it is, we just recurse on
that value, eliminating the projection and the rest of the record. If the projection is from
something other than a literal record (such as a variable), then we appeal to the function
drawn from the identity pass. This appeal is similar to a call to a method on super in
an object-oriented language.
The remainder of the functions are present in the module because we open the iden-
tity pass at the beginning. We then shadow the ones that we want to override. In
exchange for this convenience we lose exhaustiveness checking, but this is a small price
to pay for eliminating the code that would be repeated if each pass were defined over
the entire language by hand.
168
In ML5/pgh, all of the passes of the CPS phase are implemented using the Pass
functor or the pointwise functions (when they are particularly simple), except for the
type checker and code generator—these both need to look at every construct, so there is
no benefit to be had. Several of these passes are independent optimizations that can be
run at various times during compilation; these are discussed next.
5.4.4 Optimizations
A round of optimizations is performed right after conversion to CPS, before we establish
the type representation invariant (which makes optimizations more difficult to perform)
or transform the program too much (which obscures high-level structure). Making the
program as small as possible at this point also makes the later expensive phases faster.
The optimizations that ML5/pgh performs are not very sophisticated, but they are
important for preventing the code from becoming unmanageably large. I only describe
each briefly:
Dead code elimination. The most important optimization removes effectless opera-
tions whose bound variables are never used. Several other optimizations rely on this
pass to clean up after them. Since the CPS language has a free variable calculation built
in, this pass is easily implemented; for each eligible construct, we just check to see if the
bound variable is used in the body, and eliminate it if it does not.
Beta reduction. We carry out beta redices that do not increase the size of the program.
For example, if we have a non-recursive literal function value applied to some values,
we reduce this by binding the values to the argument variables and expanding the func-
tion body. There are often many beta redices after expanding the function this way as
well. A related pass is specially tailored to run after closure conversion and remove
unnecessary closures.
Eta reduction. CPS conversion can produce eta-expanded continuations, for example
in the case where the last thing that a function does is call some other function. This
purely syntactic transformation eta-reduces functions where possible. Without it, we
would not have constant-space tail calls.
169
Simplification. Primitive operations like comparisons applied to constants can be
simplified in the straightforward way. We also reduce jointext operations, which
is important because string manipulation is common in our applications.
Known. Even if we decide not to inline a function or record, we can sometimes sim-
plify operations applied to the variable it is bound to. For example, if x is bound to a
record {`1 = y, `2 = v} and we see a projection #`1 x, we can reduce this to y. This
kind of situation is particularly common for closures that escape but that have some
direct calls as well. This pass uses the arg maintained by the Pass functor to collect
this information about bound variables.
Unreachable code. We eliminate unreachable code (such as the default case of an ex-
haustive sumcase, which reduces code size and false dependencies on variables (espe-
cially the Match exception).
Here. If we have leta x = heldw v and we are at the world w, then we simplify this to
a regular value binding. This helps us recognize other optimization opportunities.
Many more optimizations are possible, of course, from classic ones to optimizations
of our modal operations like put and go. My goal is to build a usable prototype, not a
high-performance production compiler, so I have avoided spending much time on op-
timizations. Of the potential optimizations to add, the one that would probably have
the biggest payoff would be monomorphization of needlessly polymorphic (or valid)
bindings. ML5’s type inference and automatic generalization are convenient, but fre-
quently produce code that is more polymorphic than the programmer needs or wants.
This is a problem only because ML5/pgh represents types at runtime in order to per-
form marshaling, so polymorphism and validity carry a cost. This will become evident
as we begin to discuss the type representation translations in the next section.
170
type-level constructs that bind variables, such as µ, because these type variables cannot
appear in terms. The relevant constructs are therefore Λ, which binds type and world
variables; sham, which binds a world variable; and extern type, which binds a type
variable. The unpack and unpack constructs for existential types also bind a type vari-
able, but we do not expect to see these at this phase of the compiler, because they are
introduced during closure conversion.
The way that we establish the invariant is to augment each of these three so that they
bind the representation variables in addition to the type and world variables. Each is
handled in a somewhat different manner. The Λ construct abstracts over worlds, types,
and values. This gives us a convenient place to put the representations;
is translated to
Λhα1 , . . . , αn ; ω1 , . . . , ωm ;
x1 :A1 , . . . , xk :Ak ,
y1 : (α1 rep), . . . , yn : (αn rep),
z1 : (ω1 wrep), . . . , zm : (ωm wrep)i.
letsham uα1 = y1 in
..
.
letsham uαn = yn in
letsham uω1 = z1 in
..
.
letsham uωm = zm in v
by adding one value argument for each of the type and world arguments. The values
are wrapped in the modality so that they can be bound as valid variables in the body.
We use the value form of letsham because the body is a value. From this transformation
the type
∀hα1 , . . . , αn ; ω1 , . . . , ωm ; A1 , . . . , Ak i.B
becomes
∀hα1 , . . . , αn ; ω1 , . . . , ωm ; A1 , . . . , Ak ,
(α1 rep), . . . , (αn rep),
(ω1 wrep), . . . , (ωm wrep)i.B
We must also transform applications of ∀ type, since they would otherwise not pass
enough arguments. We do so by using the stand-in repfor and wrepfor values. So
v hA1 , . . . , An ; w1 , . . . , wm ; v1 , . . . , vk i
is transformed to
v hA1 , . . . , An ; w1 , . . . , wm ; v1 , . . . , vk ,
sham (repfor A1 ), . . . , sham (repfor An ),
sham (wrepfor w1 ), . . . sham (wrepfor wn )i
171
This both establishes and uses the invariant: If in repfor A the type A contains a
type variable α, our ability to generate an actual representation at this point will rely on
there being a representation of α in scope.
The extern type import is very simple; since we can know nothing about this type,
we just require that the import be accompanied by a valid value that is its representa-
tion. We expect by convention that this value is associated with a label derived from the
type’s label. Therefore
extern type α = ` in c
is translated to
extern type α = ` with rep uα = `rep in c
where `rep is the arbitrary but agreed upon way of deriving the type representation’s
label from the type’s label.
Finally, the value of shamrock type is a little trickier. We transform
sham ω.v
to
sham ω.Λh·; ·; x: (ω wrep)i. letsham uω = x in v
and so the type
ωA
becomes
ω ∀h·; ·; (ω wrep)i.A
This is straightforward except for the elimination form. Because letsham binds a
variable, we must rewrite not letsham itself but uses of letsham-bound valid vari-
ables. (We also do a similar translation for the value form, letsham.) Therefore,
letsham u = v in c
becomes
letsham u = v in (inst u w c)
where the function inst instantiates uses of u within c. For continuation expressions, inst
just finds values and applies instv to them, keeping track of the current world. The func-
tion instv u w v is pointwise in v (also tracking the current world) except on a matching
valid variable:
instv u w u = u h·; ·; sham (wrepfor w)i
This part of the translation is implemented by maintaining a set of valid variables
that were bound by letsham or letsham. This set is the arg that is threaded through
the translation by the Pass functor.
This suffices to establish the type and world representation invariant. We must now
be somewhat careful when working on the code; for example, the dead code optimiza-
tion is unsafe because it might throw away apparently unused type representations.
This will be particularly delicate during closure conversion, which is the next phase.
172
5.4.6 Closure conversion
In the closure conversion phase we will establish another invariant, which is that every
fns and Λ in the program is closed to modal and valid variables. We use this invariant
in the hoisting phase (Section 5.4.8) to pull each piece of code out to the top level so
that we can refer to them by global labels. In this phase we also translate go to take a
closure as an argument rather than a literal continuation, so that we can later marshal
this closure as data to be sent over the network.
This translation is complicated for a number of reasons. To highlight the important
aspects, I will start by giving the translation for the Λ construct, since it is non-recursive
and exposes all of the issues particular to the modal setting. I then explain how it is
extended to the mutually-recursive fns value. I will then discuss the direct call idiom,
which is an optimization to produce substantially better code in the case that functions
do not escape.
The heart of closure conversion is building an environment that contains the free
(dynamic) variables of a function, and then passing that environment as an additional
argument to the function. The body of the function then receives these free variables
from the explicit environment, rather than its surrounding context, making it closed.
The first complication in ML5/pgh is that dynamic variables include modal variables
bound at other worlds, and valid variables. We already discussed the solution to this in
the previous chapter; we use the at and modalities to encapsulate these variables in
the environment. Therefore the environment for an abstraction Λh~ ~ ; x : Ai.v will take
α; ω
the form
heldw1 x1 , . . . , heldwn xn ; sham ω.u1 , . . . , sham ω.um
where xi are the free modal variables of type Ai @ wi and uj are the free valid variables
of type ω.Aj .
The next consideration is the maintenance of our type representation invariant. In
order to preserve the invariant that each type and world in scope also has a valid repre-
sentation variable, we must also include those valid representation variables in our en-
vironment. Naı̈vely, this would include every type variable in scope. This would make
environments much larger than they needed to be. Therefore, we relax this to those
representation variables that we might actually need. Which ones might we need? This
clearly includes the representation of any type or world variable that literally occurs
free in the body of the function. It is not enough, however, to just consider these—for
example, the Λ might contain within it the continuation
go[w0 , v] call vf (v 0 )
where v 0 is free. This has no literally occurring type variables. However, compilation of
this will require us to marshal vf and v 0 . If one of these has a type involving a free type
variable α, then we will need its representation. Therefore, the right way to think of the
free type variables is as the variables of the typing derivation rather than the CPS term. It
suffices to take the set of actually occurring type and world variables, unioned with the
type and world variables that occur in the types and worlds of the free modal and valid
variables. We potentially need the representation for each of these types and worlds.
173
There is one more subtlety to the computation of the free variable set. Just as we
should not consider a variable free if it is an argument to a function, a representation
variable need not be considered free if there is already a representation for that type
being passed to the function that we are closure converting. For example, if we have
it may appear that we require a representation for ω, because it appears free in the body
of the Λ. However, we already have a representation variable in scope for the body!
It is wasteful to pass another copy, but not doing so is also required for correctness in
some cases. This is because we use the same constructs that we are closure-converting
in order to establish the invariant in the first place. In particular, in the previous phase
the sham ω.v construct was translated to
It would be a mistake to require a representation for ω in the closure for the Λ here.
If we did so, then we would need to build an environment between the sham and the Λ
that contained a representation for ω. However, we have no such representation to use,
because it is the argument to the Λ that is intended to provide the representation of the
world variable ω that was just bound.
Therefore, the translation for Λh~α; ω~ ; y:Ai.v is as follows. Let yi :Ai @ wi be the free
modal variables of the Λ, and uj ∼ω.:Bj be the free valid variables. We then let the rep-
resentable type variables αk be the be the free type variables of the typing derivation for
v, minus any among the arguments α ~ , and minus any that appear as the type (α rep)
of some value argument y. The representable world variables ωl are defined the same
way. By our invariant, for each representable type and world variable we have a valid
variable uα or uω that is its representation in scope. These are added to the set of free
valid variables uj above. Finally, the environment env is
{`y1 = heldw1 y1 , . . . ,
`yn = heldwn yn ,
`u1 = sham ω.u1 , . . . ,
`um = sham ω.um }
{`y1 : A1 at w1 , . . . ,
`yn : An at wn ,
`u1 : ω B1 , . . . ,
`um : ω Bm }
174
The newly-closed Λ, which we’ll call vl , projects out the components:
Λh~
α; ω
~ ; y:A, xe :envti.
leta y1 = #`y1 xe in
..
.
leta yn = #`yn xe in
letsham u1 = #`u1 xe in
..
.
letsham um = #`um xe in
v
And finally, the closure is the environment and the function, packed into an existen-
tial so that the type of closures is uniform:
which has the type that the annotation indicates. Recall that a pack always takes a
representation for the hidden type variable, which is embedded in the modality to
ensure its validity.
Of course, the types that are part of the annotations undergo a translation as well,
and the closure conversion transformation is applied recursively so that the any func-
tions within the body v are also closure converted; we omit that here to reduce distrac-
tions.
In addition to translating the functions into closures, we must also translate the call
sites so that they use the closure to make the call. An application
~ w
vf hA; ~ ; ~v i
where, after unpacking, we are just passing along the environment as an additional final
argument.
Mutual recursion
The mutually recursive fns construct is closure-converted in a similar way. To simplify
the discussion, let us pretend that each function in the bundle takes a single argument:
175
We compute a single environment for the entire bundle of functions. The free vari-
ables yi and uj , as well as the environment env and the environment type envt are com-
puted the same way as before.5 Inside each ck , we begin by binding all of the free vari-
ables from the environment in the familiar way. This leaves us with only thing left,
which is to account for the mutual recursion between the functions.
Within ci , we may use any of the functions fi (“friend” functions)—we could call
them (as closure calls) or pass them off to other functions. Therefore, after restoring the
free variables in the continuation body we then make a closure for each of the friends.
These are optimized away in the case that they are not used.
We use the recursive variable bound by the fns construct, but wrap it as a closure,
using the same environment xe that was an argument to this function in the bundle.
The whole fns itself is packed into an existential, along with the environment, as
before. Because we select a function out of a bundle with fsel before calling it, we also
have to translate that. The value
fsel v.n
becomes the value
It is frequently the case that an fsel is applied to a literal fns value. (This is par-
ticularly common when there is just a single function in the bundle.) In this case we
will produce a spurious pack/unpack/pack sequence; one of the optimization phases
reduces this.
A call to a continuation unpacks the closure and calls it on its argument and envi-
ronment in the obvious way; I do not give the translation here.
Go
Aside from the Λ and fns constructs, we also closure convert the body of a go, so that
we can treat it as a piece of data to later be marshaled. This is accomplished simply by
pretending that it is actually a fns bundle of a single function taking zero arguments.
We translate the go to a go cc, which expects a closure.
5
We don’t exempt a type or world on account of its representation already being among the arguments.
For one thing, it would have to be an argument to all of the functions in order for this to be safe. More
importantly, representations never appear as arguments to fns at this stage of the compiler anyway.
176
Direct calls
The closure conversion algorithm that I have described here is correct and general, but
very simplistic. In fact, most functions in most programs are used in a stylized way,
where the function is defined and then called several times within its scope, but never
placed inside a data structure or passed as an argument to another function. For func-
tions used in this idiomatic way, building an environment is more costly than is neces-
sary. (One of the big costs is that we need to store type representations even when we
are not using polymorphism in the source program, because of the existential type.)
Another form of closure conversion, which we can apply selectively instead of the
above strategy, is the direct call calling convention. After computing the free variable set,
we just add these variables as additional parameters to the function rather than putting
them in an explicit environment. Because the function does not escape, each of the calls
to it also has those free variables in scope, so we just augment the call with the variables
as additional arguments. This works for recursive and mutually-recursive functions as
well, as long as the function does not escape within its own (or a friend’s) body.
I implement the direct call calling convention for a selection of common cases, but it
ought to be extended to more. The way that we bind functions using a variety of dif-
ferent binders (val, letsham, leta) and the fact that a function may be polymorphic
makes it difficult to recognize all instances of it. (The implementation is burdened by
having to maintain the type representation invariant, as well.) However, it is worth-
while to try, because when it applies it substantially reduces code size for that function
and results in noticeably better performance.
Having completed closure conversion, we now know all of the places where type
representations might be used. We therefore want to proceed immediately to producing
real representations for them. This allows us to discard the invariant and begin treating
data as data and types as purely static.
177
representation variable associated with ω by invariant. Since worlds have such simple
structure, this is all that is needed.
For types it is only a little more complicated. At each repforA value, we recurse
over the structure of A. At a type variable α we return the associated uα . Worlds can
appear in types as well, so at a world w we appeal to the above as if we saw wrepfor w.
For any type constructor, we recurse to produce the representations of the components,
and then use the corresponding representation constructor. For example, to compute
the representation of
{`1 : A1 , `2 : A2 at w}
we compute v1 , v2 , and vw , which are the representations of A1 , A2 , and w, and then
produce
rep({`1 : v1 , `2 : rep(v2 at vw )}
The only last complication is types that bind type or world variables. For example,
when translating
∀hα; ·; ·i.A
we will produce a representation of the form
rep(∀h(α, uα ); ·; ·i.v)
where v is the representation of A. A may mention α, however, and should use the
representation uα in that case; we therefore just bind uα ∼α rep in the context as we
recurse and it works as desired.
Using marshal. The go cc construct takes an address, (remote) environment, and (re-
mote) closure-converted function as an argument:
go cc[w0 , va , ve , vf ]
To translate this, we will use marshal to produce a marshaled value of type bytes
that we can then use with go mar. At the other side—code that we will not write until
we look at the runtime system in Section 5.5.4—we will receive the bytes, unmarshal
them, and invoke the code on the environment. At the remote world, we will need to
know the type of the code and environment, so the natural thing to do is to wrap them in
an existential package so that the type is always the same. We can then pass a constant
representation to the unmarshal procedure. (This works because values of existential
type contain the dynamic representation of the hidden type, so we end up sending the
representation that way.)
Suppose the type of the environment ve is envt. Then, the code we generate is
val p = heldw0 (pack envt; repforenvt; ve , vf
as ∃α.(α, αcont)) in
val b = marshal (p, repfor ((∃α.(α, αcont)) at w0 )) in
go mar[w0 , va , b]
where the repfor is expanded to a real representation as above. Therefore, at any given
world w we always unmarshal at the type (∃α.(α, αcont)) at w.
178
Representing representations. A natural question is, now that we have generated
these type representations, what form will they actually take at runtime? In fact, the
abstract representation we are using here is totally general, in that the representation
is isomorphic to the type itself. The actual representations that we will use (discussed
in Section 5.4.9) will not contain as much information—for instance, we will marshal
every arrow type the same way, so we only need to store enough to know that it is an
arrow type. By using this most general representation, we allow ourselves the freedom
to choose any “quotient” of it at the point we do code generation. (As a disadvantage of
leaving this decision to the last minute, we may end up keeping around more dynamic
information than we will ultimately need to use, however.)
Each world has a single representation, just as each world has a single address. In
fact, these will be the same at runtime. One consequence of our world representation
phase is that we could have provided the programmer not only with a localhost op-
eration for computing one’s own address, but an addressof operation that produced
the dynamic address for a static world variable. This is probably not important for the
current prototype, where there are only two worlds that we already know the identity
of, but might be a useful feature for a future iteration.
After inserting type and world representations, we perform dead code optimizations
to remove them, if possible. We also attempt to optimize poor closure conversions now,
since these optimizations are easier to do when they do not need to be sensitive to the
type representation invariant. Once the code has been cleaned up, it can finally be
hoisted, which is the last major phase before code generation.
5.4.8 Hoisting
The purpose of hoisting is to assign each closed piece of code a unique global label, so
that we can use these labels to refer to the code. The labels will later be compiled as
integers, so that calling a function will consist of looking up the code in an array and
jumping to it. We will also use these labels as the marshaled format of code on the
network; all of the worlds will agree upon the set and meaning of these integers.
Since all of the code will be arranged in an array at the top level, we need to pull
any nested code out of its scope. Because of closure conversion, each piece of code is
closed to any dynamic (modal and valid) variables. However, it may still have free static
(type and world) variables. As we pull code to the top level, we will need to abstract
over these static variables, so that the code remains well-formed. At this stage, we also
collect all of the world constants that are declared in the program with extern val. (This
is not really related to hoisting but it is a convenient place to do it.)
To represent the hoisted program, we need new constructs in the CPS language. A
program P is a set of labels (each associated with a global G, which is some code with
its type), a set of worlds (each associated with its worldkind) and a distinguished label
179
from the set, which is the entry point of the program:
{ labs `1 = G1 , . . . , `n = Gn
worlds w1 /Jm , . . . , wm /Jm
main = ` }
G ::= vi : Ai @ wi
| ω.vj ∼ ω.Aj
In either case, the type of the global will either be ∀h~ ~ ; ·i.((A~1 ), . . . , (A~n )) conts or
α; ω
∀h~ ~ ; ·i.∀hα
α; ω ~
~0 ; ω~ 0 ; Ai.B, since we hoist only the fns and Λ constructs. They each will be
abstracted over some type and world variables so that they can be hoisted from their
static contexts. To type check the program, we introduce two new kinds of hypotheses
that can appear in the context:
`i : Ai @ wi
`j ∼ ω.Aj
for modal and valid globals, respectively. Since the globals may refer to one another
recursively, we begin by producing the context for type-checking the program, by as-
suming that the type annotations are correct. It is defined straightforwardly on the list
L of label/global bindings.
ctxfor(·) = ·
ctxfor(` = v : A @ w, L) = ctxfor(L), ` : A @ w
ctxfor(` = ω.v ∼ ω.A, L) = ctxfor(L), ` ∼ ω.A
ctxfor(L) = Γ
Γ, ω world ` vi : Ai @ ω (when Li = `i = ω.vi ∼ ω.Ai )
Γ ` vi : Ai @ wi (when Li = `i = vi : Ai @ wi )
Γ ` vi : ∀h·; ·; ·i.(()) conts @ home (for the i where Li = ` = vi : Ai @ wi )
` {labs L; worlds w1 /Jm , . . . , wm /Jm ; main = `} ok
We check that every label has the type its annotation indicates, assuming that the
others are correct. We also check that the main label is a bundle with a single continu-
ation that takes no arguments. The code makes reference to other code by using labels
as values. There are two typing rules for labels, since they might be modal or valid:
Γ, ` : A @ w, Γ0 ` ` : A @ w
Γ, ` ∼ ω.A, Γ0 ` ` : [ w/ω ]A @ w
180
Translation
With this setup, the translation is easy. We crawl over the input expression (using the
Pass functor) while maintaining a partially-built program that we update imperatively.
When we see an extern world declaration, we insert that world with its worldkind
into the set of worlds, if it is not already there. When we see a fns or Λ, we first translate
its subterms recursively. We then compute the free type variables α ~ and world variables
~ of its typing derivation. This will be a modal global if its world is a constant; other-
ω
wise, it is a world variable ω0 that must be part of the set ω~ . If modal, we abstract over
all of the free world and type variables to produce the following global:
where v is the original value and B is its type. ` is a freshly-generated label. Since
we have abstracted over all of the value’s free static variables and it has been closure
converted, this global is well-formed in the empty context.
Having inserted the global into the program, we then replace its occurrence in the
expression with `, applied to all of the static variables that we abstracted over:
` h~ ~ ; ·i
α; ω
α; ω~ 0 ; ·i
` h~
where the world ω0 is filled in by the typing rule for valid labels.
Main. The input to the hoisting phase is a CPS expression. To produce a program,
which is just a list of labeled globals, we also pretend as though the whole expression
is itself a zero-argument continuation value to be hoisted. It will already be closed in
the empty context, but we abstract over the empty list of world and type variables for
uniformity. After it has been recursively translated, we insert the whole CPS expression
into the program at a freshly generated label, and then record that as the main label.
Optimization. When we insert code into the program, we need not necessarily gen-
erate a fresh label. If equivalent code of the same type already exists in the program,
we can re-use that label instead, coalescing the two. Because the CPS implementation
gives us alpha-conversion respecting comparison between values, this is easy to imple-
ment. However, since labels are not variables, we might not recognize some collections
181
of mutually-recursive code that are equivalent up to the choice of labels without using
a more sophisticated algorithm.
After hoisting is complete, we can optimize and type check the program for a final
time, and then proceed to code generation.
182
vals v ::= i | "string" | inj` v | inj` —
| {{`1 = v1 , . . . , `n = vn }} | ptr ρ | tag "addr", i
| rep({`1 : v1 , . . . , `n : vn })
| rep(∃γ.v1 , . . . , vn )
| rep([`1 : v1? , . . . , `n : vn? ])
| rep(v1 at v2 )
| rep(∀hγ1 , . . . , γn ; γ10 , . . . , γm
0
; ·i.v)
| rep( γ.v)
| rep(πi (µ(γ1 .v1 , . . . , γn .vn )))
| rep γ | rep r | wrep "addr"
exps e ::= v | x | inj` e | e he1 , . . . , en i
| primcall `(e1 , . . . , en )
| native p(e1 , . . . , en )
| newtag | marshal e, er
| #` e | {`1 = e1 , . . . , `n = en }
| rep({`1 : e1 , . . . , `n : en })
| rep(∃γ.e1 , . . . , en )
| ...
optional vals v ? ::= v | —
rep vars γ
prim reps r ::= Rcont | Rconts | Raddr | Rrep | Rwrep | Rint
| Rstring | Rvoid | Rall | Rref | Rexn | Rtag
statement s ::= val x = e ins
| call e1 .e2 (e01 , . . . , e0n )
| halt | return e
| go[ea , eb ]
| case e of x.(`1 ⇒ s1 | . . . | elln ⇒ sn | ⇒ s)
| untag eo with et of (yes ⇒ x.s | no ⇒ s0 )
global g ::= Absent
| FunGlo ((x11 , . . . , xn1 ).s1 , . . . , (x1k , . . . , xnk ).sk )
| ValGlo ((x1 , . . . , xn ).s)
Figure 5.17: The B5 bytecode syntax. Many constructs resemble the CPS language.
183
Within the code, we refer to globals by integers, so that (for example) call expects
e1 (the global) and e2 (the offset within the bundle) to evaluate to integers. Labels `
are just strings. For allocated reference cells, we have a syntactic class of pointers into
memory ρ (these are implemented as ML ref cells).
The last new element is type and world representations. (These have both expression
and value forms; I do not repeat all of the expressions.) The representation of a world
w is always a string (wrep "w"), its address. Many type representations are primitive
constants (the syntactic class r). This is because the marshaled format for these types
is uniform. For example, a bundle of continuations with CPS type ((A~1 ), . . . , A~n ) conts
is always represented as an integer index into the array of globals, no matter what the
component types A are. The representation of any conts type is therefore rep Rconts.
Some other type representations, like the representation of a record type, are still struc-
tured. Additionally, some type representations bind representation variables, which are
in a different syntactic class γ. We will discuss marshaling and the representations that
support it in more detail in Section 5.5.4.
184
At runtime, we expect e to evaluate to an integer, which we use as an index into the
array of hoisted globals. We evaluate that global (which should be a ValGlo), which is
guaranteed to return some value because it is total.
We associate with each CPS variable (valid or modal) a B5 variable, written xy or xu :
convertv y K = K(xy )
Some CPS values bind variables, which is why the output is a statement:6
convertv (letsham u = v in v 0 ) K =
convertv v K0
where K0 (e) = val xu = e in
convertv v 0 K
Finally, we have two ways of interfacing with the local environment. When we see
an extern val, we presume that we have a corresponding variable available in the
initial context:
Execution of B5 is very simple, with the only complications coming from marshaling
and the interface with the runtime system. These are discussed in Section 5.5.
JavaScript. The JavaScript language (formally known as ECMAScript for the stan-
dards body that defines it [32]) is an untyped interpreted language whose basic con-
struct is the object. Objects are property-value associations (hashes). Some specially
named properties have special effects, such as the __proto property, which indicates
a parent “prototype” object which can be consulted in the case that a property is not
found in the child object. Some objects are native, representing handles into the DOM
6
This linearizes the evaluation of values, distorting the nested scopes of these constructs. We know
that this will not cause problems because the CPS representation ensures that all variables are globally
unique.
185
exps e ::= x | {`1 : e1 , . . . , `n : en }
| [e1 , . . . , en ] | e[e0 ]
| e1 === e2 | e1 && e2 | e1 + e2 | . . .
| i | "string" | undefined
| e.` | `(e1 , . . . , en )
statements s ::= var x = e; s
| switch(e){e1 : s1 ; break; · · · en : sn ; break; default : s; break; }
| if(e){s1 ; }else{s2 ; }
| e;
| return e;
| return;
tree or events. Manipulating these objects as data, the code takes a simple C- or Java-like
imperative form, with familiar for and if constructs, etc. as well as primitive syntax
for creating objects and selecting properties from them.
For the JavaScript code generator we ignore much of the language and use the ideal-
ized subset of its syntax shown in Figure 5.18. The expression {. . .} for creating an object
uses a colon where we have been using an equals sign throughout the dissertation; it
is not a type. The syntax [e1 , . . . , en ] creates an array with elements e1 , . . . , en , and e[e0 ]
subscripts an array. This language is much smaller than B5; we will represent almost
everything with objects, using the properties in different ways.
186
Many constructs are supported by common code in the runtime system. For exam-
ple, newtag (which creates a new tag for the implementation of extensible types) is
translated to a call to the runtime function lc newtag:
[[newtag y of A in c]]JS =
val xy = lc newtag() in
[[c]]JS
187
argument to the Λ. A bundle of continuations is compiled as an array of functions, each
taking the same number of arguments as the corresponding continuation.
An instantiation of a value of ∀ type is compiled as
convertv (v h~ ~ ; v1 , . . . , vn i) K =
α; ω
convertvs (v, v1 , . . . , vn ) K0
where K0 (e, e1 , . . . , en ) =
var x = globalcode[e](e1 , . . . , en );
K(x)
where we index into the globalcode array and call the function that is there. We
expect it to return a value.
As in B5, a function (continuation) is represented as a pair of integers: the index into
the global array of code and the offset within that bundle of functions. Thus the fsel
value is converted as
convertv (fsel v.i) K = convertv v K0
where K0 (e) =
var x = {g = e, f = i};
K(x)
and a call to a function is
[[call vf (v1 , . . . , vn )]]JS =
convertvs (vf , v1 , . . . , vn ) K
where K(ef , e1 , . . . , en ) =
lc enqueue(ef .g, ef .f , [e1 , . . . , en ]);
return;
where the runtime function lc enqueue enqueues the parameters (which represent a
thread ready to execute) and halts. When the scheduler decides to start this thread, it
indexes the globalcode array, then the bundle of continuations within that, and calls
that function with the supplied arguments.
Say. The say M keyword of ML5 produces a string containing a JavaScript expression
that, when evaluated, evaluates the ML5 expression M . At this low level, we have
say cc, which takes a set of named parameters and a closure-converted continuation.
The named parameters are used to access JavaScript events. This system exists to
work around a shortcoming of JavaScript, being that it uses a single global property
called event to store the current event within an event handler. We must read the
properties that we want from this event before the handler returns, or they will be inac-
cessible. In the CPS construct
188
here.) The value v is a continuation expression expecting a record of those values as its
argument. The translation relies mainly on functionality in the runtime, so is fairly
simple:
[[say cc y = (`1 :A1 , . . . , `m :Am ) v in c]]JS =
convertv v K
where K(e) =
var x1 = lc saveentry(e.v1.g, e.v1.f , e.v0);
var xy = "lc_runentry(" + x1 + ",{" +
"l1 : " + `1 + · · · + ", ln : " + `n +
"})";
[[c]]JS
The implementation of say relies on two runtime functions. The first,
lc saveentry, takes a closure without arguments (as the index into the globals, the
offset within that bundle, and the environment). We get these from the components of
the argument to say. This runtime function stores the closure in a table and returns the
index into that table, which can be passed to lc runentry to later execute the saved
closure on some arguments. The say construct returns a JavaScript expression as a
string, which is a call to the lc runentry runtime function on the index of the contin-
uation we just saved. It passes along in an object the values of all the event parameters.
For example, if we write in our ML5 source program
[<input type="text" onkeyup="[say { [Link] = c } keypress c]" />]
if this is the 15th entry we have placed in the table. Crucially, we read from the global
event property before returning from the handler. Since lc runentry actually just
enqueues a thread and returns immediately, this is our last chance to read from event
before it is invalidated.
189
phase is straightforward. It renames variables to be as short as possible, removes dead
code, and substitutes small pure expressions.
Output
Once the code generator has produced code for each of the worlds involved in the pro-
gram, it writes this code to disk—one file per host. This is the end of the compiler’s job.
Everything else in this chapter is concerned with how these programs are run, which is
the responsibility of a collection of software called the runtime system.
5.5 Runtime
We have been looking at details for some time; let us step back and remind ourselves
how an ML5 program is executed. From the source program, the compiler produces a
series of files—one for each host—containing the part of the program relevant to that
host and compiled to its architecture. In order to run the programs, we need a few
things: Something must cause the files to be loaded and executed, something must co-
ordinate the communication between hosts, and if the compiled programs need support
code, that must be provided as well. These tasks are all the responsibility of the runtime
system.
Aside from the fact that we had to make code generators for a concrete set of ar-
chitectures (B5 and JavaScript), the implementation so far has generally been agnostic
about the way that the programs will be run and the way they will communicate. In
fact, if we declare other worlds (of kind javascript or bytecode) in our program,
the compiler will happily output code for them. However, the current runtime system
is specialized to a two-party computation where the client is running JavaScript and the
server is running B5. These are expected to have the names home and server.
For this runtime, a compiled program consists of two files: the B5 source for the
server and the JavaScript source for the client. When the user wants to launch the ap-
plication, he does so by visiting a URL on a Server 5 web server (Section 5.5.1) that
has both compiled files available. It launches a new instance of the application on the
server (parsing and loading the B5 code) and sends a tiny web page to the client, which
includes the JavaScript runtime code (Section 5.5.3) and the JavaScript output of the
compiler. Execution begins on the client, by an effectful expression at the bottom of the
JavaScript source file that enqueues the main label (now a pair of integers and an array
of zero arguments) in the thread queue.
Since this process begins with the web server, let’s discuss it first.
5.5.1 Server 5
Server 5 is a small program (about 3500 lines of Standard ML) that listens on a network
socket and implements a fragment of the Hypertext Transfer Protocol (HTTP) [38, 39].
Over HTTP it serves static content (such as images used by applications), launches ML5
190
applications, and tunnels the protocol used by the server and client to communicate
within an application instance. It also executes the server half of ML5 applications and
provides the interface to server functionality like databases.
Network interface
val protocol :
{ make : ’a -> string,
191
parse : ’aprefix -> string -> ’a list * ’aprefix,
empty : ’aprefix } -> ’a protocol
192
/5/prog Create new session of program prog
/toclient/id Create the server to client socket for session id
/toserver/id Create the client to server socket for session id
/static/file Return the file file as a web page
/demos Show a list of demos
/source/file Display the file file as source code
Figure 5.19: The URLs that Server 5 uses to provide access to various functionality. The
toclient and toserver URLs are not accessed directly by users, but by the JavaScript
runtime.
centralized datatype enumerating the different kinds of protocols that are used in the
application; each module is responsible for implementing its own part of the extensible
set of tags itself.
This network interface was particularly useful for ConCert, where we had a number
of different packet formats in the same application. By necessity, Server 5 only listens
on a single port and uses HTTP for all of its connections. This means that there is cur-
rently only a single protocol for the entire application, making this generality somewhat
unnecessary. Still, the abstraction is desirable. For example, if we were to add support
for more than two hosts in an application, we would need a protocol to communicate
among them—HTTP would probably not be appropriate for this.
Event loop
When an HTTP request arrives to Server 5, we decide what to do with it based on
the URL that is accessed. If it is static data, we send the requested file from disk. If
it communication part of an existing session, we pass it off to that session. If it is a
request to launch a new session, we create an instance of the application, a new session
identifier, and begin execution. The possible URLs are summarized in Figure 5.19. We
begin with the URL for launching an application.
Server runtime
When the URL /5/prog is accessed by the user, the server loads the B5 and JavaScript
files that were the output of the compiler for prog (they must have already been com-
piled). The server parses the B5 code and creates an instance of the application: This
just consists of a thread queue (empty, since control begins on the client) and session
identifier (an integer). A session also maintains two references to sockets, the “to client”
and “to server” sockets. These are used to send messages between the client and server,
and are discussed in Section 5.5.2 below. They begin closed. The server then returns a
web page to the client that contains the session identifier, the JavaScript code, and the
JavaScript runtime. Since the client is where control flow begins, the server then returns
to its event loop, with this session idling while it waits for the client to initiate server
computation in it.
193
Abstractly, a message from the client to the server represents a thread of control trav-
eling from client to server via a go. (The implementation is discussed below.) When a
thread of control arrives, we add it to the thread queue. Interleaved with network activ-
ity, Server 5 is also running a simple interpreter for B5. For each session, we check to see
if it has any outstanding threads that can be run. If so, we run some. (The scheduling
policy is round-robin and tuned for responsiveness rather than throughput.) A thread
can only run for a finite time before yielding because of the way that we have compiled
programs: Recursion is the only way for a program to loop, but calls are compiled to
push threads into the thread queue rather than looping eagerly. Therefore we do not
need to worry about preemption; we run the threads to completion and go on to the
next round. Additionally, if there are any queued messages from the server to the client
and we are in a position to send them, we do so.
Server 5 also provides some functionality that programs can access directly, such as
a rudimentary database (Section 5.1.4). The only interesting part of this is the imple-
mentation of addhook. Recall that it is imported to ML5 as
extern val [Link]
: string * unit cont -> unit @ server
This allows us to register a thread that will be executed whenever the indicated key is
updated. The implementation of the database just associates a list of such hooks with
each key, and when a key is updated, the continuation is launched in the same way
as a message from the client causes a continuation to be run. Because the database is
shared by all of the application instances running on Server 5, this gives us a limited (but
usually sufficient) way of having different application instances interact. We will see
several examples of this in Chapter 6. Of course, the right way to support applications
with more than two hosts would be to use the expressive language that we have gone
through so much trouble to build. The ability to do so is limited by runtime support,
with the chief limitation being the JavaScript security model. I describe this and the way
that we work around it in the next section.
5.5.2 Communication
The JavaScript security model is based on traditional sandboxing techniques (for pre-
serving memory safety on the client machine) and the same origin policy for enforcing ac-
cess control. The same origin policy asserts that JavaScript code can only access things
(DOM nodes, network sockets, etc.) that are from the same origin, where the “origin” is
taken to be the host, port, and URL that served the JavaScript [61, 120]. Furthermore, the
only way that JavaScript is able to communicate with other hosts is by actively opening
a connection;7 it is not possible for it to open a listening socket and wait for connections
from other hosts.
7
There are in fact many ways of doing this to work around various browser quirks and attempts
at patching security holes. These range from the straightforward one that we use here to dynamically
generating HTML content that includes tiny invisible images (whose loading creates a minute covert
channel with the server that hosts them).
194
This causes trouble for the ML5 model, where the server can initiate a thread on
the client with go at any time. The solution is standard in web programming practice:
The client preemptively makes a request to the server that the server delays responding
to until it wants to send a message. When it sends a message, the client destroys the
connection and creates a new one to replace it.
To make this work, the server keeps a queue of messages that it wants to send to the
client. The client attempts to always keep open a connection to the server by fetching
the toclient URL whenever it does not have an outstanding request. This has the
nice side effect of serving as the application’s keep-alive; even if the application is idle
and not making requests of the server, it still must create this connection. If it does not,
then the server can assume that the client has left the page and destroy the session to
reclaim resources.
This technique is reasonably simple and works well, because we always make a
connection back to the server that originally provided the JavaScript code. Unfortu-
nately, allowing for connections between clients or to other servers is much more diffi-
cult because of the same origin policy. The most general way to support multiple hosts
would be to build an overlay network where messages were routed through the origin
server(s) in a way that respected the same origin policy. This would be a nice next step
for Server 5.
195
if (lc_recsleft === 0) {
lc_threadqueue.enq( { g : g, f : f, args : args } );
setTimeout(lc_schedule, 10);
lc_recsleft = THREADPACE;
} else {
lc_recsleft --;
var f = globalcode[g][f];
[Link](undefined, args);
}
};
This version eagerly continues execution for 6 steps before yielding, by decrement-
ing a counter and then immediately scheduling the thread. This is a tradeoff between
the responsiveness of the user interface and the throughput of the JavaScript computa-
tions. We can not set it very high before being troubled by recursion limits or making
the application behave badly, but small factors give an almost linear speedup. A better
implementation would have a finer cost model for the underlying code [136]; right now
we only measure the cost in terms of the number of calls. The cost of each function can
vary widely depending on the program and how it happened to be compiled.
We arrange that we only run the scheduler when there are threads in it, so we never
waste time repeatedly checking an empty queue. We do this by installing a timer only
when we have added a thread to the queue, so that there is exactly one outstanding
JavaScript timer for each ML5 thread in the queue. This means that the thread queue
only consists of threads that are ready to run.
There are other sources of “waiting” threads, however, such as event handlers in
the UI and messages from the server. These become active through mechanisms other
than the thread queue. For example, JavaScript provides the XMLHttpRequest object,
which is used to fetch the toserver and toclient URLs and establish communica-
tion with the server. The interface to XMLHttpRequest is asynchronous, meaning that
we give it a handler that runs whenever it receives data from the server. The handler
for our toclient connection checks to see if it has read a complete marshaled message
(which consists of a closure to execute); if so, it unmarshals the closure and puts it in
the thread queue. It then makes a replacement toclient connection to wait for further
messages. Event handlers that we placed on UI elements using say can also enqueue
threads; these come from the table of continuations that say saves into.
The client runtime also provides the implementation of some functionality that we
import using extern val. Though the interface we give for the DOM is very low-
level, it nonetheless requires brief stubs to implement it, since DOM operations are typ-
ically properties of the objects rather than “static” methods. For example, the function
that changes the UI focus to a DOM element is imported in ML5 as
extern val [Link] : [Link] -> unit @ home = lc_domfocus
and implemented in the runtime as
function lc_domfocus(node) {
[Link] ();
196
};
Most of the JavaScript runtime consists of the marshaling and unmarshaling rou-
tines, which are the subject of the next section.
Marshaling. The marshal function takes the value and a representation of its type
and returns a string. It also takes a marshaling context ∆, which maps variables γ to
type and world representation values. We need this because some representations have
binding structure. Finally, marshal keeps track of an optional concrete world w? , which
is the world of the value being marshaled. The marshaling context begins empty and
the world begins as the world doing the marshaling.
Most of the cases of marshaling are very simple, and just consist of converting the
value to a string in an arbitrary but consistent way. Rather than bother with the details
of how these strings are actually represented, I pretend as though marshaling produces
a list of tokens, which can be strings, integers, or other lists of tokens. For example, the
case for marshaling tags is as follows:
That is, to marshal a tag (a value consisting of the address of the host that created the
tag, as a string, and a unique integer), we produce the address and integer. A less
degenerate case is for records:
In this case we just recursively marshal each of the component values along with the
name of its label.8 The marshaling of integers and strings, injections, continuations
(which are pairs of integers), addresses, type and world representations themselves,
and most other values are just straightforward recursive representations of their syntax.
I do not give those here, instead discussing only the remaining cases that are interesting.
The marshaling function is defined by case analysis on the type representation, not
the value being marshaled. For example, the held constructor is present at run time, but
8
We could be more efficient by insisting that the values be sorted by name, or by not using named
labels at all, but that is an optimization for a later time.
197
we do represent the at type:
Here we recursively marshal the same value at the representation vt . We also change
the current world, by using the read function, which is defined as
The world may be either a literal world representation (in which case we extract it), or
it may be a representation variable, in which case we look it up in the context. In the
context it may have the value “—,” the reason for such optional worlds are discussed in
the case for below.
The reason that we record the current world is so that we can specialize the mar-
shaling of certain values—specifically local resources—based on the world that we are
marshaling them at. Suppose that we are marshaling a local mutable reference cell.
We use the primitive Rref representation for mutable references and other local re-
sources, so the value v could have the form ptr ρ, for example. Even if we knew that
it had this form, we cannot directly marshal pointer into memory ρ. This is because
we cannot get access to this pointer in JavaScript or ML, nor would we want to send
raw pointers over the network—it would interfere with garbage collection, for instance.
What we do instead is called desiccation: we store v in a local table and marshal the in-
teger index into that table instead. We only want to do this if the reference is a local
reference. The representation Rref does not tell us whether it is a local or remote refer-
ence, so this is where we use the world parameter to marshal. Supposing that we are
marshaling on the server, then the case is
where i is the index of v after it has been inserted into our local table. If the world does
not match, then the case is
In this case, we are marshaling a local reference that belongs to some other world. If
we have such a reference, then it was desiccated before it was sent to us, so it is locally
represented as an integer. Therefore, we expect an integer and marshal it as an integer.
Reconstitution of desiccated values takes place in unmarshaling, which is described
below.
The representation might be a variable, in which case we look it up in the context:
198
Note that this rule applies for any value v. We bind representation variables as we
recurse. For example, inductive types are marshaled as follows:
Because the roll coercion is erased at runtime, the value could take any form (in practice
it is an inj, because these come from source language datatypes). We will continue
marshaling the same value at an unrolled version of the representation. We extend the
context to bind the representation variables γ; each one is bound to the representation
of the corresponding projection from the µ. This case of marshaling does not make the
representation smaller (by some measure), so it is how we are able to marshal arbitrarily
large objects without necessarily having representations of the same size.
Three other constructs bind variables during marshaling. First, a value of existential
type is marshaled as follows:
The representation of ∃α.A ~ binds a variable to the representation of α. This comes from
the d field of the value that has that type—since the hidden type could be many dif-
ferent things, we can not know it ahead of time. We marshal it and send it, since the
unmarshaling side will need to get the representation dynamically as well; when mar-
shaling the representation we use the representation of representations, rep Rrep. This
is followed by the marshaled values, which are marshaled in the extended marshaling
context.
Marshaling of ∀ and types is subtle because they are universal quantifiers, not
existential ones. This means that there is no one existential witness to bind to the repre-
sentation variable. Because the quantifier is universal, however, we can instead choose
any instantiation of it that we like. Let’s take a ∀ quantifier that abstracts only over types
as an example:
We choose to instantiate the universal type at the empty type void, and therefore bind
all of the representation variables to rep Rvoid. Marshaling at this representation al-
ways fails, but there are no values of void type! Similarly, there is no value of type
∀hα; ·; ·i.α (or else there would be a value of type void). On the other hand, we do have
values of type ∀hα; ·; ·i.(α cont). The marshaling of a continuation does not depend on
the types of its arguments, so instantiating α with void here does not cause trouble (we
199
never look up the corresponding representation variable γ). As another example, the
sum type [`1 : int, `2 : void] is inhabited, but only by a value of the form inj`1 n. This
means that we can marshal the compiled version of the polymorphic empty list nil, be-
cause the arm of the sum type that it occupies does not mention the polymorphic type
variable.
The fact that we can choose to instantiate a universal quantifier any way we want is
our justification for marshaling ∀ this way. That we never try to marshal at the Rvoid
representation is a consequence of a correct implementation. In fact, since a correctly
compiled program should never use the representation variables bound by ∀, an opti-
mized implementation should probably not represent the ∀ type at all. Either way, it is
important to observe that this is what we are doing.
The ω type constructor is also a universal quantifier. We treat it in the same spirit
as ∀, but have no direct analog of the void type for worlds. This is the reason that the
world argument to marshal is optional:
marshal ∆ v (rep ( γ .vr )) w? = marshal (∆, γ = —) v vr —
When we marshal a value of type, we bind the representation variable to “—” and also
begin marshaling at no concrete world. This is slightly different than the case for void in
that there are many values that are well-formed irrespective of the world (the cases for
records and integers do not care about the world at all, for example). The only marshal-
ing cases that are sensitive to the world are the ones that desiccate local resources. There
are no local resource values that are well-formed at every world, however, so these cases
do not arise. Worlds can also appear in some types. If this universally-quantified world
appears in an at type, we will just set the current world to “—” like we did with . They
can appear in some other values, like the type of the address of a world. Again, there is
no value of address type that works for all worlds; every address is for a specific world.
Therefore, we should not encounter these cases either. Like ∀, we might as well not even
represent the constructor—but it is worthwhile to observe our justification for doing
so.
For the same reasons, in the full version of the rule for ∀, we bind the representation
variables for the world arguments to “—”; the case is
marshal ∆ v (rep(∀hγ1 , . . . , γn ; γ10 , . . . , γm
0
; ·[Link] )) w? =
marshal (∆, γ1 = rep Rvoid, . . . , γn = rep Rvoid, γ10 = —, . . . , γm
0
= —) v vr w?
200
When we finish unmarshaling, we have a local continuation and an argument for it, so
we can insert this in our local thread queue.
Unmarshaling is simple in most cases, reversing the work done by marshaling. For
example, to unmarshal a value of sum type (when the arm carries a value):
.
unmarshal ([`] + l) (rep [` : vr , ..]) w? =
(inj` v, l0 )
when unmarshal l vr w? = (v, l0 )
Unmarshaling treats the at modality, the universal quantifiers, and inductive types
the same way that marshaling does. For existential types,
We begin by reading the representation of the hidden type from the list of tokens, using
the representation of the type of representations, (rep Rrep). We then bind that to the
representation variable and read the sequence of values using the sequence of represen-
tations that are components of the ∃.
The inverse of desiccation is reconstitution. When unmarshaling a reference, we again
have two cases depending on the world:
The marshaled format of a reference is always a desiccated integer; if it is not our own,
then we continue to represent it as an integer. If it is at server, however, we read the
value from our local table:
Other considerations
Sharing and cycles. Marshaling does not preserve sharing (where two identical ob-
jects live are allocated to the same place in memory). Sharing is important for some
functional algorithms in order to get good space behavior. In the Grid/ML marshal-
ing implementation we used pointer comparisons to preserve sharing and cycles [85].
We do not have direct access to pointer values in JavaScript, which makes sharing-
preserving marshaling hard to implement. Our best strategy would probably be hash
consing [6, 36], which would also have the advantage of finding accidental sharing in
201
addition to the sharing already present, but would be quite costly. On the other hand,
cycles pose no problem because they can only come from mutable references. We don’t
look at the contents of mutable references, because we marshal them by desiccation. In
any case, the design of the language does not constrain our implementation of marshal-
ing much: only that we need to be able to marshal any value. Since desiccation is a
fairly general way of handling local resources like reference cells and DOM handles, the
remaining data can be marshaled using whatever technology is appropriate.
Garbage. One problem with desiccation, however, is that it accumulates values in the
local table. Currently, these can not be reclaimed. To do so, we would have to implement
distributed garbage collection, because the last outstanding reference to a desiccated
value might be as an index in a data structure at another world. Distributed garbage
collection is possible in principle, but quite difficult. Fortunately, we only pay this cost
when we use certain mutable structures and local resources—the local garbage collec-
tors in the web browser and Standard ML runtime can collect anything else. I do not
think that these unreclaimable resources are a big problem for our application domain.
Instances of web programs are usually short-lived, and users tolerate web browsers
that leak memory for existing web applications. An implementation that additionally
allowed for long-running programs would clearly be useful, however.
Data format. The server and client communicate using marshaled strings in the bodies
of HTTP requests. One good thing about using a mainstream protocol like HTTP for
communication is that network software usually assumes that the traffic is desirable
and therefore allows it through proxies. Unfortunately, some software also assumes that
it understands the contents of the request, and therefore will modify it! (For example,
some VPN software will rewrite the page to insert a control panel and modify links to
pass through the VPN.) I found it necessary to wrap the marshaled message in a tag
<ml5>s</ml5> and use a custom MIME type [41] in order to work around proxies; it
is not clear how general this solution is, but hard to accept blame for it not working.
5.6 Summary
In this chapter I described the programming language ML5, which is an ML-like lan-
guage for distributed computing based on the modal typing discipline that we derived
from the modal logic IS5 (Chapter 3). The language retains the spirit of ML: it is stati-
cally typed, higher-order, call-by-value, and has support for both functional and effect-
ful programming. Our modal typing judgment integrates cleanly into the language,
leading to an extension of the usual polymorphic type inference algorithm to type,
world, and validity inference. Owing in part to type inference, SML code in the com-
mon subset generally type-checks without modification. In this sense the distributed
features and typing discipline do not interfere with programs that do not make use of
them.
202
I also described the type-directed ML5/pgh compiler, which follows the same basic
strategy as the abstract compilation I formalized in the previous chapter. It is extended
so that we can implement first-class continuations, algebraic datatypes and extensible
types, type polymorphism, an interface to local resources via network signatures, run-
time type representations for marshaling, and exceptions. The compiler is supported
by a runtime system, including a web server and interpreter for the server’s bytecode
language, as well as implementations of the marshaling algorithm for both client and
server.
Implementing a language is an intellectually valuable exercise even if we never use
it, but it is clearly also interesting to try to write programs in our language! The imple-
mentation currently works well enough to support some realistic demo web applica-
tions. These are the subject of the next chapter.
203
204
Chapter 6
Applications
In this chapter I describe some of the applications that we have built using ML5. These
applications are intended as demonstrations and exercises of its features, so they are
somewhat small. Nonetheless, they suggest that with some more engineering, ML5
could be used to build full-scale applications, and show us what features work well and
which ones need to be improved.
The screenshots in this chapter have been modified slightly to make them more ap-
propriate for print. The applications can also be run online at [Link]
ml5/ or by installing and running Server 5 on a local computer.
6.1 Watchkey
In developing these applications I tried not to implement too much functionality on
the server in Standard ML or the client in JavaScript. This is because I didn’t want
ML5 code to just be “glue” between code written in native languages (although it can
clearly be used this way)—most of the computation should be expressed in ML5. One
consequence is that the database that all of the applications use is very rudimentary. It
is imported on the server with the following network signature:
extern val [Link] : string -> string @ server
extern val [Link] : string * string -> unit @ server
extern val [Link]
: string * unit cont -> unit @ server
It associates string keys with string values. We use this simple database for persistent
storage in all of the applications. Let’s look at a simple use of it before moving on to the
real applications.
The Watchkey demo displays the current value of a database key on the server, and
allows the user to modify that key. A screenshot appears in Figure 6.1. It is good to see
one working program in its entirety; the source code is as follows:
205
Figure 6.1: A screenshot of the Watchkey demo. The upper box (with DOM id showbox)
displays the current value of the database key; it is asynchronously updated whenever
the key is changed on the server. The input form on the bottom (with DOM id inbox)
allows the user to set the key by pressing the button.
unit
import "[Link]"
import "[Link]"
import "[Link]"
put k = [tdb-test]
fun getkey () =
let val v = from server get [Link] k
in [Link] ([Link] [showbox], [innerHTML], v) end
fun setkey () =
let put s = [Link] ([Link] [inbox], [value])
in from server get [Link] (k, s) end
do [Link] ([Link] [page], [innerHTML],
[[k]’s value:
<div class="show" id="showbox"> </div> <br />
<input class="in" type="text" id="inbox" /> <br />
<div onclick="[say setkey ()]"
class="button">set key</div> ])
do from server
get [Link] (k, cont (fn () => from home get getkey ()))
end
We begin by importing header files that define the standard environment and import
the external resources for interacting with the client DOM and server database. The
database key that we use in this application is tdb-test, which is bound to the valid
variable k. We use k on both the client and server below. (Note that we do not have to
use put here to make the binding of k valid—the binding would be generalized because
the right hand side is a value.) The function getkey retrieves the value of the key
from the server and modifies the page to display it. It uses get to evaluate a database
read expression on the server, and then uses the DOM routines to modify the page on
the client when it returns. The display box (which is later created as part of the page)
will have the DOM id showbox; setting its innerHTML property changes its displayed
contents. The setkey function reads the value of the input box (with id inbox), binds
206
it to a valid variable s so that we can use it on the server, and then goes to the server to
update the database. Both of these functions have type unit → unit @ home; they are
modal because they access the DOM tree, which is local to the client.
The next declaration has the effect of setting the entire page to the HTML document
given. The document contains the HTML elements that getkey and setkey refer to.
It also contains an element that handles the onclick event by calling setkey. The
CSS classes [11, 69] show, in, and button just change the presentation of the elements
so that (for example) the clickable button has a raised outline. The declaration of these
styles are not important to the behavior of the program and so I don’t show them.
After creating the page, we also make a trip to the server to register a hook for when
the key changes. We use the addhook function to do this. The hook that we send goes
home and runs the getkey function to update the contents of the display.1 The cont
function is defined in the standard library; it converts a function of type unit → unit
and to a unit cont.
That’s it! The remainder of the applications use a similar model of creating a web
page with event handlers and modifying its contents in response to activity from the
user or asynchronous updates from the server. Since they are much larger, we will not
want to look at the entire source code. Instead, we will discuss their design, the most
interesting parts of their implementation, and how the features (or lack of features) in
ML5 help and hinder us.
6.2 Chat
One consequence of using a persistent centralized database is that multiple instances of
the same application see the effects of each other’s database modifications. For example,
two instances of the Watchkey demo can see each other’s updates to the key. The first
application is a generalization of this mode of use to a two-party chat program.
The design of this application is based on having a database key for each of the two
players, representing their current message. (We actually have two keys per player, the
“old message” and the “current message”.) These have hooks installed, like in Watchkey,
so that the two players see updates to the keys as they are made. There are two phases
of the application. When it starts, the user selects which of the two players he is, and
then it proceeds to the actual chat.
Selecting the player consists of displaying a web page with two buttons on it. This
screen appears in Figure 6.2. First class continuations give us an elegant way to display
this prompt and react to the selection of a player. The beginning of the program looks
like this:
put (us, them) =
letcc ret
1
This program can be made more efficient by reading the key before it goes to the client, so that it
does not need to make another round trip. This optimization could plausibly be done by the compiler,
in fact, because the get obeys certain equivalences. Such an optimization would be useful and a nice
consequence of using high-level language features to express the concepts of distribution.
207
Figure 6.2: Choosing the player in the chat application. Each of the buttons has an
event-handler that continues at the top-level of the program with bindings for the player
selected.
in
let fun pick p = throw p to ret
in
[Link]
([Link] [page],
[innerHTML],
[<div class="heading">Chat! Choose your player:</div> <br />
<span class="button"
onclick="[say pick ([chat.1], [chat.2])]">player 1</span>
<span class="button"
onclick="[say pick ([chat.2], [chat.1])]">player 2</span>
]);
halt ()
end
end
We are making a declaration of the pair of variables us and them, which are the
database keys for this player and the opposite player. (If we choose player 2, then us
is chat.2 and them is chat.1). We can’t wait in a loop for the prompt to return,
because this would lock the user out of the interface; instead we must react to actions
initiated by the user. To do so, we save the current continuation (of type (string ×
string) cont @ home). The function pick takes a pair of strings and throws them to the
return continuation that we just saved. We then modify the page to display the prompt.
It has two buttons with event handlers. The “player 1” button calls pick with the keys
for the first player, and similarly for player 2. We then terminate the current (top-level!)
thread by calling the standard library function halt. The page waits idle until the user
triggers one of the two event handlers, at which point the appropriate strings are bound
by the put and we continue to the later top-level declarations, which enter the chat
mode.
208
One shortcoming of this approach, which is a problem with all of our applications, is
race conditions. It is conceivable that (depending on how the program is compiled and
scheduled) the user could click both buttons and thus launch two concurrent threads
running the remainder of the application. This would not cause the application to crash,
but it would behave strangely (two competing server hooks would be added for each
of the keys, for example). There are a number of solutions we could contemplate for
this. It would be easy to add rudimentary locking or atomicity to the language because
JavaScript execution is already single-threaded. The intention that an event handler (or
an external disjunction of event handlers) be linear is common in these applications—
that it (and related alternatives) should only be used once. This would also be simple
to implement because JavaScript is single-threaded. In any case, a proper treatment
of user-interface concurrency is outside the scope of this dissertation, and so I do not
worry about it much in these applications.
After selecting the us and them keys, we proceed to replacing the page with the chat
interface. This appears in Figure 6.3. The chat bubbles are just styled <div> elements,
whose bodies we update as in the Watchkey demo. They show the value of the “old” key
(in grey) and the current key (in black). The input form at the bottom is the only source
of user input. It has an event handler registered for keypresses:
[<input type="text"
class="typebox"
onkeyup="[say { [Link] = c }
case c of
?\r => input-send ()
| _ => send-all ()]"
id="[[Link]]" />]
The send-all function reads the input box and sends its contents to the server
to be saved at the us key. Because the other player will have a hook attached to this
key, he will see our message as we type it. If the return key is pressed (the keyCode
is the character \r) then we call the input-send function. This replaces the “old” us
key with the value of the current one, and blanks the current key, to visually indicate
that the player has committed to his message. This was the impetus for adding event
parameters to the say construct; without them there is no way to detect that the user
has pressed the return key.
That’s all there is to this application. There are a couple things that could be im-
proved about it. First, the application could automatically assign players to slots (or
support an arbitrary number of concurrent players). To do so it would have to use the
database to arrange that each player is assigned a unique identifier and key, and that
the list of active players is sent to each of them. The database is not really the most
appropriate structure for doing this. Moreover, since ML5 is already designed to sup-
port multiple hosts in the same computation, it would be better to build the application
that way and improve the runtime to support multiple hosts in an application. I dis-
cuss some ways this could possibly be accomplished in Chapter 7. Another problem is
that the application sends a lot of redundant messages. For each keypress, we make a
209
Figure 6.3: Chatting in the chat application. The bottom bubble is the user’s chat, and
the top is the opposite player’s. A form tracks the user’s input as he types.
210
round-trip to the server and send not just that keypress but the entire string. Moreover,
the thread model does not guarantee that these threads will run in order, so an earlier
keypress could overwrite a later one. The cost of sending the entire input each time is
not serious, since the fixed overhead of a message is the overwhelming factor for the in-
put sizes we’re concerned with here. Sending too many messages and the possibility for
sending them out of order is a more serious problem, however. In the next application
we will see one way of avoiding this problem.
6.3 Wiki
The next application is a Wiki, which is a collection of hyperlinked documents that are
easily editable by the user. The idea of this application is to associate each document
with a database key, and provide an interface for navigating between keys and editing
them. Its most important feature is that each document is rendered using a custom
markup language. All of this rendering is performed on the client.
The interface and markup language are designed to mimic the popular MediaWiki
software [71], which is used for Wikipedia [143]. Unlike MediaWiki, navigation between
documents is performed all within the same web page (rather than using the browser’s
native hyperlinks). To begin editing a page, the user clicks on the “edit” tab at the top,
which displays the editable document source without leaving the page. A screenshot of
the editing interface appears in Figure 6.4.
This application would be just like the Watchkey demo if not for the client-
side page rendering. As the user modifies the page source, a thread parses the
markup language and generates the rendered HTML for the display. The syntax
[[dest|text]] creates a link to the page dest that displays with the text text.
These links are not real hyperlinks but colored text with an onclick handler gener-
ated by say that navigates to the referenced page. The wiki also supports the syntax
{{tmpl|s1|arg=s2|arg2=s3|s4}} for templates. A template is a parameterized
page fragment that can be used in the construction of other pages. The arguments are
separated by a vertical bar, and can be named or unnamed. (For example, the heading
template used in the screenshot renders its unnamed argument at a larger size followed
by a horizontal rule.) Within the definition of the template (itself just a page), uses of the
arguments are written {{{name}}} for a named argument or {{{n}}} for the nth un-
named argument. Rendering a page therefore may require the contents of other pages.
Additionally, because rendering takes place while the user is typing, the render function
must have reasonable behavior when the document ends with incomplete syntax like
{{tmpl|x=.
Most of the Wiki implementation consists of the hand-written parser and renderer.
They are written in a straightforward ML fashion: As a collection of mutually recursive
functions using datatypes and pattern matching to represent tokens and parsed phrases.
The parser was originally a major performance bottleneck in the Wiki application—
the original version was too slow to be used for anything more than a few dozen char-
acters. This is due to the poor design and performance of JavaScript in general, and
211
Figure 6.4: A screenshot of the Wiki application, while editing a page. The editable text
box at the bottom is the page’s source code and the display at the top is its rendered
version.
212
the quality of the JavaScript code produced by ML5/pgh. Since I don’t have any con-
trol over JavaScript, improvements could only be made to the compiler. Many of the
optimizations in ML5/pgh were included to specifically address problems with the
code generated for this parser. The biggest improvement came from using a more ea-
ger scheduling strategy that executed several basic blocks in a thread before yielding
(Section 5.5.3). This is because the tokenization code is a small tight loop over the in-
put string, which otherwise causes an expensive yield for every character. Including a
primitive intcase construct in the CPS language improved the inner loop, which was
previously compiled as a series of if tests for character equality. Many more optimiza-
tions are possible for the compiler, but what is currently implemented is enough for the
Wiki rendering code to run at a tolerable speed.
A consequence of a slow renderer is that we can get very bad behavior from race con-
ditions and overeager updates. For example, if every keypress triggers a render (which
can take up to a second for medium-sized pages), we will certainly have multiple ren-
derers running concurrently on different input, each attempting to update the display
with an outdated version of the page. The Wiki is therefore designed in a different way.
We maintain two reference cells that store time values: the time that the page source
was last modified, and the time that we read the page source that is currently rendered
and displayed. Keypresses just update the modified time. If the modified time is more
recent than the rendered time, then we must begin a new rendering thread to update the
display. We perform this check at periodic intervals by using JavaScript timers. (These
are just like the event handlers we place on UI elements, but that trigger after a spec-
ified delay.) Each check and potential render is guarded by a lock that prevents two
outstanding renders from occurring simultaneously. This mutual exclusion routine
maybe-with-lock l f
checks to see if the lock l is currently held. If so, it discards f and does nothing. If
it is not held, it atomically takes the lock and executes f , then releases the lock when
f completes. The correctness of its implementation (which is part of a library) relies
on knowing that certain sequences of operations do not yield; it would be much better
to have language support for mutual exclusion and have the compiler guarantee the
semantics.
We also have a reference cell that keeps track of the last time that the page was saved
on the server, and a periodic timer that saves it. If the page has changed but has not been
saved yet, then a message is displayed; the user can trigger a save or render manually
by clicking one of the buttons in the interface.
The final interesting thing about the application is the treatment of templates. Dur-
ing the rendering of a page we might notice that we need the contents of another page
in order to use it as a template. We could retrieve it from the server with get, but this
would make rendering even slower. Instead, we keep a local cache of templates that we
use for rendering. When rendering, if we do not have the value of a template yet, we
render a placeholder. A separate thread retrieves the value of the template and updates
the cache; whenever the cache is updated we also mark the page as modified so that it is
213
Figure 6.5: Lambdasheet screenshot showing the expression in cell G4 being edited.
rendered again. Finally, we install a hook for each template in the cache so that we can
be asynchronously notified if one of them is modified (by another user, for example).
Aside from the mediocre performance of rendering, this wiki is fairly close to what a
small real wiki would offer. Perhaps the only major missing feature is a way of tracking
the history of each page—which would be easy to implement if the server had support
in its database interface. On the negative side, the locking scheme used by the imple-
mentation is more complicated than I would like for such a small application. Writing
concurrent programs is difficult, but high level structures for correctly implementing
common idioms (at a minimum) would be desirable here.
6.4 Spreadsheet
The next application is a simple spreadsheet called Lambdasheet. A screenshot appears
in Figure 6.5. It is a 12 × 12 matrix (the screenshot is truncated for space) containing
named cells. Each cell contains an expression which may include the values of other
cells. An expression can be a literal number or string, a cell name like G4, or an arith-
metic expression in prefix form. For example, as shown in the screenshot, the expression
in cell G4 is * G2 G3, the unit cost times the number of items. Each cell also has a value
derived from its expression. By default the spreadsheet is displaying only the values.
Clicking on a cell allows the user to edit the expression. (Lambdasheet was co-written
with Rob Simmons.)
The expressions in lambdasheet are stored in the database on the server, and as the
values of HTML input boxes inside the DOM. These input boxes are hidden except
when the user clicks to edit a cell. We synchronize the values of these cells with the
database using a hook for each cell. (This means several users in different instances can
edit the spreadsheet at the same time.) In addition to the expressions, we have an array
of the same size as the matrix, which stores the last evaluated value of each cell’s expres-
214
sion. We don’t use the DOM to store the authoritative version of the values, because we
want to distinguish the string value 3 from the numeric value 3 (even though they both
display the same), for example. A loop runs periodically over all of the expressions in
the spreadsheet, evaluating them using the current values of the cells in the value array,
and updating those. This allows expressions to be recursive, with each successive pass
computing the next iteration. Expressions are parsed and represented as ML datatypes,
and evaluation is a straightforward pattern-matching ML function.
Lambdasheet is interesting in that it stresses the network performance of ML5. For
example, if we used polling to update each cell from the server rather than asyn-
chronous notification, then we would poll 144 times in each period! Asynchronous
notification makes this efficient and reduces the update latency. Another instance of
this is as follows: In each of these applications, at the point that we register a hook for
a key, we also want to get the original value of that key. The typical pattern for this is
to execute the hook function (as if the key had changed) at the same time we attach it.
An early version of Lambdasheet did exactly this, but this resulted in very bad perfor-
mance when the spreadsheet was loaded, as 144 round-trips from server to client to call
the updated-cell function were initiated. Fortunately, this was easy to solve. As we
register the hooks on the server, we insert any non-empty keys into a list of key-value
pairs. Then, we return only this list to the client in a single message—its type is mobile,
as a list of pairs of mobile types. On the client we map the updated-cell function
over the list, registering the initial values all at once. The code is only a few lines long,
and completely solves the performance issue by using data structures and higher-order
functions.
6.5 Summary
In this chapter I presented a few web applications built with ML5. They all share the
common feature of connecting local resources (a user interface in the web browser and
the server-side database), making them true distributed computations. In some applica-
tions we also perform substantial computation on the client. Although the applications
are small, they resemble real web applications in everyday use, with the major differ-
ence being features and scale. On the other hand, our high level language makes it
simple to do things (such as threading and asynchronous notifications) that normally
take a large engineering effort using conventional tools.
A natural question is, how did ML5 help us in building these? It is difficult to eval-
uate this question objectively, because to some extent language choice is a matter of
taste, skill level varies greatly between programmers, and measures of code complex-
ity or programmer effort (such as lines of source code) are very coarse. However, I
will nonetheless argue for ML5’s benefits. As mentioned, its high-level language fea-
tures like get, higher-order functions, first-class continuations and algebraic datatypes
with pattern matching make programs shorter and simpler to express. Second, even
when used by experts (such as the language designer!), the type system does exclude
programs with erroneous use of local resources in practice. A handful of times while
215
developing each of the applications, I encountered type errors (sometimes initially be-
lieving them to be bugs in the compiler) that indicated unsafe programs. Since no type
system can be exact, the rate of false positives is also important. For these examples, I
never found the type system to prevent a safe program that I wanted to write. Finally,
and most importantly, ML5’s modal type system yields a logical way of organizing a
program according to the places involved (worlds), their local resources (modal code
and data), and the computations and values that they share (valid code and data). This
simply leads to better programs.
There are still some problems with ML5 and its implementation that make it in-
appropriate for building production-quality applications today. We have discussed
some already, such as performance and language support for concurrency. In the next
chapter—which concludes the dissertation—I discuss concrete future improvements as
well as more speculative ideas. I also discuss related work, including some that could
serve as a way of addressing some of these deficiencies, and work that could potentially
benefit from the ideas prevented so far.
216
Chapter 7
Conclusion
In this chapter I conclude with a comparison of ML5 to related work (Section 7.1) as
well as a discussion of some outstanding research problems and ideas for the future
(Section 7.2).
Located programming. ML5’s main contribution to the design of languages for dis-
tributed computing is a logical, type-based account of located programming: Program-
ming in a uniform language with an explicit notion of place (Chapter 2). Many pro-
gramming languages used for distributed computing identify hosts in the computation
using URLs (represented by strings) or IP addresses. These are reasonable run-time rep-
resentations, but seldom do they find their way into the type system. This situation is
not well-suited for located programming, because the programmer is unable to express
the differing perspectives on code and data and his program except by informal means
like naming conventions and documentation. An expressive type system is useful for
structuring one’s program in a logical way, but programmer discipline is not the only
thing at stake. Lack of support for locations in the type system also leads to problems
with the design of the language’s distributed features. A common symptom of such
inexpressiveness is the premature termination of a program that attempts to send a ref-
erence to one of its own local resources to another party in the computation. To be
concrete, suppose that we have two parties in the program, A and B. A has a local open
217
file descriptor f that it wants to send to B. (This situation is not artificial; it is particu-
larly common when f is within the environment of a closure.) If it tries, the program is
either excluded statically (rare, except when the set of types of data that are allowed to
be sent is extremely limited) or fails at runtime during marshaling. Both behaviors are
needlessly conservative, because this program is safe as long as B does not try to read
from or write to A’s file descriptor f . It can safely send f back to A, for example.
Let us diagnose how a missing language concept of location leads to this result.
Without being able to mention the principals A and B, “A’s file descriptor” and “B’s
file descriptor” just become “file descriptor.” (These English phrases correspond to the
typing judgments of the programming language, because we use the type system to
classify code and data.) Because the language must prevent an access to f from B’s
code, it must make sure that a file descriptor always refers to a local file. The standard
way to do this is to identify the fault as being at the moment that f is sent to B. It is at
this moment that we change perspective (running on A to running on B) and therefore
that our notion of (local) “file descriptor” changes as well. Therefore f is no longer a
file descriptor—the program must be rejected (statically or dynamically).
Located programming allows us to think more clearly about this situation, because
it allows us to express the multiple simultaneous perspectives. Not only can we be
explicit about where the code is running (by using the modal typing judgment on
code), but we can simultaneously manipulate data that belong to various principals
(by using the typing judgment on hypotheses, and using the types it engenders to
describe data). In particular, code running on A can safely hand off a file descriptor
(f :file @ A) to code running on B. It does this by first encapsulating it to mark that it
belongs to A (hold f :file at A @ A), then observing that the marked datum is portable
(file at A mobile) and thus also makes sense from B’s perspective (file at A @ B). Al-
though B can now manipulate the encapsulated value (perhaps sending it back to A),
it is statically prevented from erroneously reading from it, as desired. This mindset
allows us to decouple the implementation technique of marshaling (which now never
fails) from the semantic quality of mobility.
Some languages address this problem in another way: transparent mobility. In such
a language the above program does not fail, but if B reads from f , then the system au-
tomatically forwards these reads to A and the results back to B. In doing so, the type
“file descriptor” now means “global file descriptor”—forcing a universal viewpoint of
data. This is legitimate and often what the programmer wants, but is not only what a
programmer wants. In ML5, we allow the programmer to implement global resources
by using local resources and mobility. These global resources can be given this univer-
sal viewpoint through the validity judgment. Therefore, such global resources are still
expressible when using located programming (perhaps provided as libraries), but we
additionally have the ability to be specific when we desire.
218
calculus [56] extends the π-calculus to support distribution by wrapping processes with
explicit locations. A process can migrate to a new location via a construct goto, and
new locations can be created at runtime. The language addresses the control of access
to local resources (represented as names) by permitting a location to restrict the types of
code that can migrate to it. The type system is sophisticated, allowing the specification
of complex behaviors.
Nomadic Pict [125, 135] extends the π-calculus based programming language
Pict [109] to distributed processes. Each process again is marked with an explicit lo-
cation. The low-level calculus has only local communication primitives. The high-level
language implements unrestricted communication between processes at different loca-
tions by translation to the low-level language through various strategies.
This line of research on process calculi attempt to address similar problems as does
ML5: locality; mobility; local and global resources. They also begin from foundational
calculi and their semantics rather than implementation concerns. However, the feel of
the work is very different, making a direct comparison difficult. In general, the primi-
tives of process and channel lead to a natural focus on concurrency and communication—
two facets that we have deemphasized in this work. Lambda 5’s basis in logic instead
naturally leads to a focus on types, data, and reduction-based operational semantics. It
also permits straightforward integration with functional programming languages like
ML that are already based on logic, and is compatible with traditional compiler tech-
niques. Because my goal is to give an account of the spatial component of distributed
computing—and to implement it—the logical approach fits well. Nonetheless, we have
observed that concurrency support is important, even in the simple applications I have
experimented with. Perhaps process calculi can provide the foundation for concurrency
support in ML5?
219
the propositions of modal logic as types, and giving them a computational interpreta-
tion based on proof reduction. This gives us a type theory (which allows us to logically
structure programs and prove certain properties about the language, like type safety)
for our programming language, rather than a theory for proving properties of specific
programs.
Moody [80, 81] gives a lambda calculus based on the constructive modal logic S4 of
Pfenning and Davies [107]. This logic’s accessibility relation is reflexive and transitive,
but not symmetric, and the presentation relies on judgments A true (here), A valid and
A poss rather than the indexed truth judgment of IS5∪ . As a result, worlds do not appear
explicitly in the judgments or types. The propositions are therefore somewhat “lossy”
in the sense we discussed in Section 4.1. As a result, the 2 and 3 connectives are in-
terpreted as representing potential mobility and locality, with a type-safe process-style
operational semantics to match.
More closely related is Jia and Walker’s computational interpretation of hybrid
S5 [62]. As we have discussed, this was the source of our at modality. They also have
a notion of validity, where the 2 connective is the source of valid hypotheses. (Unlike
Lambda 5’s validity judgment and modality, these do not bind a world variable.) The
biggest difference in Lambda 5 is the presence of the get structural rule (and the mobile
judgment), and its restriction of the other rules to act only on local data. Without these,
Jia and Walker’s calculus has a process-style operational semantics that requires syn-
chronization across worlds. In contrast, Lambda 5’s operational model can be given in
terms of proof reduction, with standard substitution of values for variables being the
main force of computation.
Because in Lambda 5 and ML5 evaluation can take place in different locations, we are
not always in a position to evaluate an expression into its value. Therefore, we found it
important to distinguish between the modal typing judgment as applied to expressions
(code) and values (data). For example, the A type classifies mobile values of type A, and
the typing rules for the expression hold M and value held v are different. Park [100, 101]
studied the problem of mobile values in an S4-style modal logic by introducing a new
modality. His type A classifies computations that return mobile values of type A. (In
contrast, 2A classifies mobile computations that return possibly non-mobile values of
type A). This modality was the original inspiration of Lambda 5’s modality. The
modality is somewhat more simple, in that we do not have a validity judgment for
expressions— A represents not a computation producing a mobile value of type A,
but an already evaluated mobile value of type A. (Since ultimately in the compiler
we require “values” to include some forms of pure computation, this is perhaps not a
difference after all.)
Park refines this calculus to λPC , a call-by value parallel language with communica-
tion primitives [102]. By using the validity judgment and its internalization as a type,
the language is able to statically prevent local resources (references) from being sent
through a channel to another thread. They do this by splitting a communication chan-
nel into two distinct parts: its read half and its write half. The read half remains fixed
to the thread that created it, and the write half is allowed to be a global value. Channels
are restricted such that only global values can be sent over them. This is overconserva-
220
tive in the sense described above: Programs are rejected because they try to share remote
resources, not because they try to improperly use them. Specifically, if the read half of
a channel always belongs to (say) thread g1 , any value that is sent along the write half
always ends up in g1 . Therefore, the channel ought to be restricted not to carry a global
value, but a value that makes sense specifically in the thread g1 . There is no way to ex-
press the condition more tightly in λPC , since when typechecking the code for the thread
g2 , we have only two typing notions available to us: ”here” (wrong, since here means
g2 , not g1 ) and ”global.”
221
ing in the type system that a piece of code or data is well-formed independent of its
location.
Acute [123, 124] for distributed computing based on O’Caml. Rather than commit to
a high-level model of interaction like ML5’s get, they provide type-safe access to low-
level features like marshaling so that the programmer can implement his own. Acute’s
goal is somewhat different, in that it allows for the development of programs with-
out full static knowledge of the environment in which they will run. This is impor-
tant for distributed applications where hosts may be discovered at runtime or that are
upgraded in a piecemeal fashion. To accomplish this, Acute supports two ways of re-
solving a resource in a piece of code that arrives at a remote host. A representation of
the resource can simply be sent along with the code, or the resource can be rebound
to a local one—appropriate for library code, for example. Acute uses hashes of mod-
ule definitions and other techniques to guarantee type safe marshaling in the presence
of abstract types. This technique was later extended to account for type generativity
and polymorphism in HashCaml [7, 8]. ML5 does not attempt to maintain abstraction
at runtime, instead assuming that all of the code was produced by the same version
of the source program. Like Acute and HashCaml, the ML5 intermediate languages
use type representation passing to implement type-safe unmarshaling. However, Acute
and HashCaml are built on the O’Caml marshaling routine, which already has enough
information without type representations to guide the marshaling process. Therefore,
type representations are only used for a compositional run-time equality (rather than a
recursive inspection of their structure), so they are represented much more compactly
as 256-bit hashes. In ML5 we are using the type representations as disembodied tags for
the native representations, which gives us more flexibility but also makes them more
costly.
The Alice language [117, 118] is an extension of Standard ML to permit “open
programming”—dynamic integration of software components in a distributed pro-
gram. Its design is therefore closely related to Acute’s, allowing resource dependencies
to be sent along with components or dynamically rebound upon arrival at a site. Alice’s
facility of pickling [119] allows for the type-safe marshaling of arbitrary values (includ-
ing higher-order code and modules), but fails dynamically if it attempts to marshal a
local resource.
ML5 is based on a more static view of distributed programs. It is clear that this
suffices for at least simple web applications, since the set of worlds and the resources
involved is small and fixed. However, because they are based in type theory, support
for dynamic components such as in Acute and Alice would probably integrate into ML5
in a clean way. In fact, Alice-style higher-order modules might give us a natural way
to discover worlds at runtime, by allowing abstract worlds to be a third component of
modules along with values and abstract types.
222
a unified programming language for web applications. Current development practice
often involves several programming languages and tools within the same application
(JavaScript, Java, PHP, SQL, etc.) and ad hoc structures for communicating between
them (hand-written text- or XML-based protocols, string-based SQL query generators,
etc.).
QHTML [33] is a module for the Oz language [127] that allows it to be used for web
application development. It has a language for declarative GUI specifications (from
which HTML is derived) and a thread model for responding to UI events. Thread mo-
bility between client and server is transparent. QHTML works by embedding a Java
applet within the web page and using it to run Oz code and as the liaison between the
client and server.
Links [21] is a language designed particularly for web programming. It has a
JavaScript-like syntax but an ML-like semantics. Links has explicit distribution: Func-
tions may be annotated as client or server, and Links allows calls between client and
server code. However, its type system does no more to indicate what code and data
can be safely mobile, and marshaling can fail at runtime. On the other hand, Links has
many features (such as a facility for embedding and checking XML documents and gen-
erating database queries from Links code) that make typeful web programming easier.
It additionally supports a mode of page-oriented application where all of the session
state is stored on the client, as marshaled data inside of hyperlinks and forms. This
can be used to increase performance by reducing the amount of persistent storage on
the server. In contrast, ML5 only supports “AJAX” style web applications (i.e., a single
page that the user never leaves), because our symmetric computational model requires
that the server be able to contact the client at any time.
Hop [122] is another unified web programming language, based on Scheme. Hop
has constructs for embedding a client side expression within a server expression and
vice-versa, analogous to get in ML5 (but specific to the two-world case). The chief
difference is simply that Hop is untyped, and thus subject to run-time failures.
Swift [19] is a web programming language based on the Java variant Jif [93]. In
Swift, a web program is annotated with information flow properties about the secrecy
and authenticity of data. The program is then compiled (if possible) into client-side
JavaScript and server-side Java that respect the security annotations given. Distribu-
tion and communication are not explicit in the program, so compilation is achieved by
program partitioning. The program is partitioned in such a way as to minimize commu-
nication. Swift represents one solution to an important security problem that faces ML5
and other distributed programming languages; this is further discussed in Section 7.2.
There is much potential for future work on modal type systems and ML5.
223
7.2.1 Modal type systems
Interpreting the modality. Lambda 5 was designed as a computational modal logic
where worlds are interpreted as places in a distributed computation. However, at this
abstract level there is nothing that forces us to interpret the worlds spatially. Indeed,
there have been many other uses of computational modal logics, interpreting the worlds
as security principals [1, 2, 44, 65], stages of computation in a metaprogramming lan-
guage [29], steps in a time-ordered sequence of computations [28, 35, 57], possibilities
in the decision making processes of agents [114], etc. Broadly speaking, whenever a
setting has multiple simultaneous perspectives, a modal logic may be good way to rea-
son about it and a modal lambda calculus may be the basis of a natural programming
language. Lambda 5’s decomposition into locally-acting introduction and elimination
rules and the perspective shifting get structural rule may be useful for these other in-
terpretations of modal logic as well. Of course, we have to make sure that our choice of
accessibility, as well as semantic notions such as the mobile judgment, are appropriate
for the application. For example, a temporal logic in which time may symmetrically
step backwards and forwards would be suspicious!
Wide-area physical distribution is also not the only spatial interpretation of worlds.
Computer architectures are becoming increasingly non-uniform, leading to a smaller-
scale notion of locality. Even though virtual memory machines present a flat address
space abstraction, the performance characteristics differ substantially between data that
reside in register-shadowed stack slots, L1 and L2 caches, main memory, memory-
mapped files, and disk. (The semantics of these can differ as well, for instance with
floating-point precision and memory coherence in multiprocessor environments.) A
type system for a low-level language could allow the programmer to make these loca-
tions explicit using the spatial modality of Lambda 5. The Java-based X10 language [18]
is designed for such non-uniform memories, but its notion of place is not logically de-
rived.
224
because the completeness of the logic with local introduction and elimination rules de-
pends on the structural rule get. The get rule has some notion of symmetry built into
it, because it transfers the locus of reasoning to the remote world and then returns a
proposition. Therefore, its analogue in a non-symmetric network is not clear. We can
see this in the translation of get to the CPS language go, in which we bind the return
address as a valid variable. Interestingly, the go construct therefore does not have such
symmetry built in, so the CPS-style language is more straightforwardly compatible with
a non-universal accessibility relation.
Multiple worlds. Our type theory naturally supports an arbitrary number of worlds,
and most of the compiler does, as well. Adding the ability for a program to access many
different servers would just be a matter of adding runtime support for it. This would
take the form of an overlay network to circumvent JavaScript’s same-origin security
policy. Applications that have a single client and multiple servers are not as easy to
motivate as applications with a single server and multiple clients; we have already seen
some examples that would benefit from this latter model. This would take a bit more
design work, because we currently consider the thread of control to begin on the (one)
client. To support multiple clients, we would want to begin the thread of control on one
server, and then instantiate a piece of world-polymorphic code for each new client that
connected. This would mean adding a primitive for handling a new HTTP connection,
so that some part of what Server 5 does would become part of the application code.
Fault tolerance. I have so far ignored the issue of fault tolerance, which is a major
focus of research in distributed computing. For ConCert we built a system that auto-
matically tolerated host failure by repeating a computation on another host [85]. We
were able to do this because all computations were total, portable between machines,
and automatically allocated by the runtime. The ML5 programming model is much
lower-level in that the programmer explicitly chooses where a computation will run. In
the case that the host is unreachable or fails while the computation is active, there is not
much hope for automatic recovery since the computation would need to be run in that
same failed place. A simple notification (by raising an exception, for example) could
225
inform the programmer and allow him to handle the failure in an appropriate way—
perhaps explicitly choosing to re-run the computation on a different host. It is helpful
that interaction between hosts only happens via the get construct, so the programmer
can identify the potential failure points in his program and know what actions are pos-
sible once a failure has been detected. Failure tolerance is very important for large-scale
computations, because the probability of failure rises as the number of hosts involved
increases. However, for our example domain of web applications it is not a serious
problem: If one of the two hosts fails, then there is not much to do except abort the
application. (The ML5/pgh runtime currently does detect communication failure and
display an error message.)
Marshaling. The ML5/pgh runtime does only simple data validation during unmar-
shaling. Therefore, if the marshaled data is accidentally or maliciously corrupted, this
can lead to runtime failures. Because we unmarshal with respect to a type represen-
tation, we can easily add more checks to ensure that the runtime failure happens at
unmarshaling time rather than later. Currently the runtime assumes that the network is
reliable and the participants trustworthy. (This is mainly because I do not address more
difficult security issues, which are discussed below.) There are a variety of ways that
we could improve the robustness of unmarshaling, for example by using cryptographic
signatures to maintain the authenticity of data [3].
Security. A more serious security problem faces us when some of the participants in a
distributed computation are malicious. This is a realistic scenario for web applications—
although the application author can usually assume that the server is secure, web sites
are often deployed publicly. JavaScript code intended to run on the client is actually
226
under the complete control of an attacker. He can inspect its source and cause it to
behave arbitrarily, and invoke any continuation on the server for which he is able to
craft acceptable arguments. This is true of any distributed system where some hosts are
controlled by attackers, and the programmer must defend against this by not trusting
(and explicitly checking) data and code it receives from the client. For example, take the
following program:
extern val format_hard_drive : unit -> unit @ server
extern val prompt_password : unit -> string @ home
put p = prompt_password ()
do from server
get if p seq pass
then let in
from client get alert "Formatting...";
format_hard_drive ()
end
else let put u = errormsg
in from client get alert errormsg
227
end
In this version we have explicitly typed the variable pass at the server so that the
client will not have access to it. We could arrange for the ML5/pgh compiler to en-
crypt data typed at the server, since we know it won’t be used in other places. We
then perform the password check on the server, where we trust the program to behave
as written. Unfortunately, this program also has security problems. First is that the
process of CPS and closure conversion leaves the client with a continuation and en-
vironment containing the values of the variables pass and errormsg. They may be
encrypted, preventing the client from reading them directly, but he can modify the clo-
sure to swap the values of the two variables! When control returns to the server, it will
either succeed with the password equivalence check (if the client typed in the string
"Wrong password!", which is now the value of pass) or will return to the client to
display the errormsg—which is now the secret password. Alternatively, the client can
invoke a different continuation directly (by guessing the code label and environment
for it); for example returning from the get that displays the Formatting... message
after the password check has been successful. Neither of these behaviors is easy to ex-
plain without considering the way that the program is compiled; the first modifies the
contents of immutable variables, and the second involves a non-local flow of control.
228
7.2.3 Web programming
ML5 specifically as a web programming language could be improved in a few ways,
as we observed in Chapter 6. Two of the most common tasks in a web application
are interacting with server-side resources (usually a database) and manipulating user
interfaces on the client side. The current prototype has very rudimentary support for
each of these.
Databases. Accessing a database is simple: We can create a datatype for SQL queries
(for example) and use the network signature to expose an interface to a database engine
of our choice. Since database access is so pervasive, we might consider language exten-
sions. Links has support for database queries based on a technique from Kleisli [12, 144]:
Semantically, a primitive database operation returns a stream of all results from the
database, and standard features from the programming language are used to imple-
ment the query. The compiler translates the functional manipulation of the stream into
the database language (such as SQL) to produce an efficient query. This has the advan-
tage of being semantically lightweight, requiring almost no new language features. I
do not like it, however, because it requires the programmer to anticipate when the com-
piler will be able to produce an efficient database query. Being able to reason about the
efficiency of queries is critical when data sets are large.1 Therefore, I prefer that the lan-
guage have direct support for expressing the kinds of operations that the database can
perform efficiently. The main benefit of a language extension in this case is that we can
type-check the queries and their results. Such an embedding is fairly straightforward.
Client pages. The current way of creating and modifying the client user interface is
dissatisfying. We use strings to write HTML concrete syntax directly, which is compact
but not subject to any static checks. JavaScript event handlers are similarly represented
as strings and are essentially untyped. We could build a high-level client library for
interacting with the DOM using the features we already have, or consider language ex-
tensions that allow us to express XML documents using a convenient syntax and appro-
priate type system. There has been much interest in this problem from the programming
languages community. JWIG [20] is a Java extension that uses a flow analysis to check
that generated HTML is well-formed. Elsman and Larsen give a Standard ML library for
constructing XHTML documents, which uses phantom types to ensure that the docu-
ments are well-formed [34]. WASH/CGI [131] is a monadic library for Haskell [103] for
the creation of well-formed XHTML documents. It uses type classes to statically encode
(most of) the type structure of XHTML. WUI [48] is a similar typed monadic combinator
library for the functional logic language Curry [26]. The main purpose of WASH/CGI
and WUI are to provide a language for sessions with the web server that are translated
to uses of HTML forms and CGI. These features are not important for ML5, which is
1
Database systems also perform substantial query optimizations as well. However, these optimiza-
tions are often explicitly specified and tools such as SQL’s EXPLAIN statement exist to diagnose perfor-
mance implications.
229
committed to AJAX-style [45] interaction. Hop [122] simply uses Scheme functions for
each of the HTML tags to ensure that they are properly balanced; it is untyped. Links’s
syntax and type system are designed for the purpose of producing well-formed XML
documents [21]. Gardner et al. [43] give a minimal formalization of DOM and a Hoare-
like logic for stating properties and proving them of simple programs that manipulate
the DOM.
With a complete library or language for writing XML DOM documents, we could
replace the string-generating say with a construct that produces an abstract type of
event handler, which can only be used in the appropriate contexts.
7.2.4 Conclusion
In this dissertation I have argued that modal type systems provide an elegant and prac-
tical means for controlling local resources in spatially distributed computer programs.
The project is a complete study in programming language design, beginning from the
identification of the problem (an inexpressiveness resulting a lack of multiple simulta-
neous viewpoints), to the design and implementation of a new programming language
to address the problem, and the crafting of applications in that language to study its
effectiveness.
The specific contributions are summarized as follows: I developed a spatial formu-
lation of modal logic and derived the lambda calculus Lambda 5 from it. The logic
admits cut and the calculus is given a type-safe distributed operational semantics based
on standard substitution-based proof reduction. I then showed how this calculus could
be extended to account for distributed control flow and global resources in a logical
way. Each of these systems were formalized in LF and their metatheoretic properties
proved in Twelf; the proofs are therefore machine-checkable. Next, I developed an
abstract typed compilation technique for the modal lambda calculus, including CPS
conversion and closure conversion. These transformations were proved to be sound,
also in machine-checkable form. To argue for the practicality of modal type systems,
I then designed a high-level language ML5 that combined features from ML with the
new modal constructs. The new features integrate naturally into ML-style languages,
retaining for example full polymorphic type inference. Using the abstract compilation
technique I formalized, I then implemented this language as a type-directed compiler.
The compiler is specialized to web applications, a particular kind of distributed compu-
tation involving two hosts: the web server and the web browser. The compiler produces
code in different languages for the server and for the web browser, and a runtime sys-
tem including a web server and type-directed marshaler ties them together. Using this
implementation, I then developed a collection of applications to exercise the language’s
features and evaluate its effectiveness at solving the stated problem. Although there
are a few remaining issues to be addressed before ML5 can be used for production-
quality code, the results are encouraging. The language is expressive enough to build
realistic applications, performance is acceptable with much room left for improvement,
and its type system excludes runtime failures while encouraging a logical structure of
programs based on the places (worlds), local resources (modal code) and shared com-
230
putations (valid code) involved. This leads to simpler, more reliable, and more elegant
distributed programs.
231
232
Appendix A
Twelf proofs
% Propositions
% Natural deduction
% Structural
get : mobile A -> A @ W -> A @ W’.
% Implication
=>I : (A @ W -> B @ W) -> (A => B @ W).
=>E : (A => B @ W) -> A @ W -> B @ W.
% Necessity
!I : ({o:world} A @ o) -> ! A @ W.
233
!E : ! A @ W -> A @ W.
!G : ! A @ W’ -> ! A @ W = [a] get !mob a.
% Possibility
?I : A @ W -> ? A @ W.
?E : ? A @ W -> ({o:world} A @ o -> C @ W) -> C @ W.
?G : ? A @ W’ -> ? A @ W = [a] get ?mob a.
% Hybrid
atI : A @ W -> A at W @ W.
atE : A at W’ @ W -> (A @ W’ -> C @ W) -> C @ W.
atG : A at W @ W’ -> A at W @ W’’ = [a] get atmob a.
% Conjunction
&I : A @ W -> B @ W -> A & B @ W.
&E1 : A & B @ W -> A @ W.
&E2 : A & B @ W -> B @ W.
% Disjunction
|I1 : A @ W -> A | B @ W.
|I2 : B @ W -> A | B @ W.
|E : A | B @ W -> (A @ W -> C @ W) -> (B @ W -> C @ W) -> C @ W.
% %%%%%%%%%%%%%%%%%%%%%
% IS5U sequent calculus
% %%%%%%%%%%%%%%%%%%%%%
!R : ({o:world} conc A o)
-> conc (! A) W.
!L : (hyp A W’ -> conc C U)
-> (hyp (! A) W -> conc C U).
?R : conc A W’
-> conc (? A) W.
?L : ({o:world} hyp A o -> conc C U)
-> (hyp (? A) W -> conc C U).
234
(hyp (A at W) W’ -> conc C W’’).
cut : {A:prop}
conc A W ->
(hyp A W -> conc C U) ->
conc C U ->
type.
%mode cut +A +D +E -F.
% Expansion
exp : mobile A -> hyp A W -> conc A W’ -> type. %name exp AM.
%mode +{A:prop} +{M:mobile A}
+{W:world} +{W’:world}
+{H:hyp A W} -{C:conc A W’}
exp M H C.
235
+{H:conc A W} -{C:conc A W’}
shift M H C.
- : shift M (init H) D
<- exp M H D.
% Initial cuts
ci_l : cut A (init H) ([h] E h) (E H).
ci_r : cut A D ([h] init h) D.
% Principal cuts
c_=> : cut (A1 => A2) (=>R ([h1] D2 h1))
([h] =>L (E1 h) ([h2] E2 h h2) h) F
<- cut (A1 => A2) (=>R ([h1] D2 h1)) ([h] E1 h) E1’
<- ({h2:hyp A2 W}
cut (A1 => A2) (=>R ([h1] D2 h1))
([h] E2 h h2) (E2’ h2))
<- cut A1 E1’ ([h1] D2 h1) F1
<- cut A2 F1 ([h2] E2’ h2) F.
236
<- ({ha}{hb}
cut (A & B) (&R Da Db) ([h] E h ha hb) (E’ ha hb))
<- ({hb} cut A Da ([ha] E’ ha hb) (E’’ hb))
<- cut B Db ([hb] E’’ hb) F.
237
cl_|L : cut A (|L Da Db H) E (|L Fa Fb H)
<- ({ha} cut A (Da ha) E (Fa ha))
<- ({hb} cut A (Db hb) E (Fb hb)).
% Structural
ns_get : ndseq (get MOB N : A @ W) (F : conc A W)
<- ndseq N (D : conc A W’)
<- shift MOB D F.
% Disjunction
ns_|I1 : ndseq (|I1 N) (|R1 D)
<- ndseq N D.
% Implication
ns_=>I : ndseq (=>I ([u1] N2 u1)) (=>R ([h1] D2 h1))
<- ({u1:A1 @ W} {h1:hyp A1 W}
ndseq u1 (init h1) -> ndseq (N2 u1) (D2 h1)).
ns_=>E : ndseq (=>E N2 N1) D
<- ndseq N2 D’
<- ndseq N1 D1
<- cut (A1 => A2) D’ ([h] =>L D1 ([h2] init h2) h) D.
% Necessity
ns_!I : ndseq (!I ([o] N1 o)) (!R ([o] D1 o))
<- ({o:world} ndseq (N1 o) (D1 o)).
ns_!E : ndseq (!E N1) D
<- ndseq N1 D’
<- cut (! A1) D’ ([h] !L ([h1] init h1) h) D.
% Possibility
ns_?I : ndseq (?I N1) (?R D1)
<- ndseq N1 D1.
ns_?E : ndseq (?E N1 ([o][u1] N2 o u1)) D
<- ndseq N1 D1’
<- ({o:world} {u1:A1 @ o} {h1:hyp A1 o}
ndseq u1 (init h1) -> ndseq (N2 o u1) (D2 o h1))
<- cut (? A1) D1’ ([h] ?L ([o][h1] D2 o h1) h) D.
% Hybrid
ns_atI : ndseq (atI N) (atR D)
238
<- ndseq N D.
ns_atE : ndseq (atE N1 ([u] N2 u)) D
<- ndseq N1 D1’
<- ({u}{h} ndseq u (init h) -> ndseq (N2 u) (D2 h))
<- cut (A at W) D1’ ([h] atL ([h’] D2 h’) h) D.
% Conjunction
ns_&I : ndseq (&I N1 N2) (&R D1 D2)
<- ndseq N1 D1
<- ndseq N2 D2.
ns_&E1 : ndseq (&E1 (N : A & B @ W)) F
<- ndseq N D
<- cut (A & B) D ([h] &L ([ha][hb] init ha) h) F.
ns_&E2 : ndseq (&E2 (N : A & B @ W)) F
<- ndseq N D
<- cut (A & B) D ([h] &L ([ha][hb] init hb) h) F.
239
- : eraseh ([x] !E (D x)) (!E D’) <- eraseh D D’.
% Init
sn_init : seqnd (init H) N <- hypnd H N.
% Necessity
sn_!R : seqnd (!R ([o] D1 o)) (!I ([o] N1 o))
<- ({o:world} seqnd (D1 o) (N1 o)).
sn_!L : seqnd (!L ([h1] D2 h1) H) (N2 (!E (!G N)))
<- ({h1:hyp A1 W’} {u1:A1 @ W’}
hypnd h1 u1 -> seqnd (D2 h1) (N2’ h1 u1))
<- ({u} eraseh ([h] N2’ h u) (N2 u))
<- hypnd H N.
% Possibility
sn_?R : seqnd (?R D1) (?G (?I N1)) <- seqnd D1 N1.
sn_?L : seqnd (?L ([o][h1] D2 o h1) H)
(?E (?G N) ([o] [u1] N2 o u1))
<- ({o:world} {h1:hyp A1 o} {u1:A1 @ o}
hypnd h1 u1 -> seqnd (D2 o h1) (N2’ o h1 u1))
<- ({o}{u} eraseh ([h] N2’ o h u) (N2 o u))
<- hypnd H N.
% Hybrid
sn_atR : seqnd (atR D : conc (A at W’) W)
(get atmob (atI N : (A at W’) @ W’))
<- seqnd (D : conc A W’) (N : A @ W’).
sn_atL : seqnd (atL ([h] D h) H) (atE (atG N1) ([u] N2 u))
<- ({h : hyp A W}{u : A @ W}
hypnd h u -> seqnd (D h) (N2’ h u))
<- ({u} eraseh ([h] N2’ h u) (N2 u))
<- hypnd H N1.
% Conjunction
sn_&R : seqnd (&R D1 D2) (&I N1 N2)
<- seqnd D1 N1
<- seqnd D2 N2.
sn_&L : seqnd (&L ([ha][hb] D ha hb) H) (N (&E1 Nab) (&E2 Nab))
<- ({ha : hyp A W}{ua : A @ W}{t : hypnd ha ua}
{hb : hyp B W}{ub : B @ W}{t : hypnd hb ub}
seqnd (D ha hb) (N’’ ha hb ua ub))
<- ({ha}{ua}{_:hypnd ha ua}{ub}
eraseh ([hb] N’’ ha hb ua ub) (N’ ha ua ub))
<- ({ua}{ub} eraseh ([ha] N’ ha ua ub) (N ua ub))
<- hypnd H Nab.
% Implication
sn_=>R : seqnd (=>R ([h1] D2 h1)) (=>I ([u1] N2 u1))
<- ({h1:hyp A1 W} {u1:A1 @ W} hypnd h1 u1 ->
seqnd (D2 h1) (N2’ h1 u1))
<- ({u} eraseh ([h] N2’ h u) (N2 u)).
240
sn_=>L : seqnd (=>L D1 ([h2] D2 h2) H) (N2 (=>E N N1))
<- seqnd D1 N1
<- ({h2:hyp A2 W} {u2:A2 @ W} hypnd h2 u2 ->
seqnd (D2 h2) (N2’ h2 u2))
<- ({u} eraseh ([h] N2’ h u) (N2 u))
<- hypnd H N.
% Disjunction
sn_|R1 : seqnd (|R1 D) (|I1 N) <- seqnd D N.
sn_|R2 : seqnd (|R2 D) (|I2 N) <- seqnd D N.
% Implication
==>I : (A @@ W -> B @@ W) -> (A => B @@ W).
==>E : (A => B @@ W) -> A @@ W -> B @@ W.
% Necessity
!!I : ({o:world} A @@ o) -> ! A @@ W.
!!E : ! A @@ W -> A @@ W’.
% Possibility
??I : A @@ W -> ? A @@ W’.
??E : ? A @@ W’ -> ({o:world} A @@ o -> C @@ W) -> C @@ W.
% Hybrid
attI : A @@ W -> A at W @@ W’.
attE : A at W’ @@ W’’ -> (A @@ W’ -> C @@ W) -> C @@ W.
% Conjunction
&&I : A @@ W -> B @@ W -> A & B @@ W.
&&E1 : A & B @@ W -> A @@ W.
&&E2 : A & B @@ W -> B @@ W.
241
% Disjunction
||I1 : A @@ W -> A | B @@ W.
||I2 : B @@ W -> A | B @@ W.
||E : A | B @@ W’ -> (A @@ W’ -> C @@ W) -> (B @@ W’ -> C @@ W) -> C @@ W.
% Disjunction
- : nndseq (||I1 N) (|R1 D) <- nndseq N D.
- : nndseq (||I2 N) (|R2 D) <- nndseq N D.
- : nndseq (||E D DA DB) F
<- nndseq D D’
<- ({x : A @@ W}{ha}{_:nndseq x (init ha)}
nndseq (DA x) (DA’ ha))
<- ({x : B @@ W}{hb}{_:nndseq x (init hb)}
nndseq (DB x) (DB’ hb))
<- cut (A | B) D’ ([h] |L DA’ DB’ h) F.
% Implication
- : nndseq (==>I ([u1] N2 u1)) (=>R ([h1] D2 h1))
<- ({u1:A1 @@ W} {h1:hyp A1 W}
nndseq u1 (init h1) -> nndseq (N2 u1) (D2 h1)).
- : nndseq (==>E N2 N1) D
<- nndseq N2 D’
<- nndseq N1 D1
<- cut (A1 => A2) D’ ([h] =>L D1 ([h2] init h2) h) D.
% Necessity
- : nndseq (!!I ([o] N1 o)) (!R ([o] D1 o))
<- ({o:world} nndseq (N1 o) (D1 o)).
- : nndseq (!!E N1) D
<- nndseq N1 D’
<- cut (! A1) D’ ([h] !L ([h1] init h1) h) D.
% Possibility
- : nndseq (??I N1) (?R D1)
<- nndseq N1 D1.
- : nndseq (??E N1 ([o][u1] N2 o u1)) D
<- nndseq N1 D1’
<- ({o:world} {u1:A1 @@ o} {h1:hyp A1 o}
nndseq u1 (init h1) -> nndseq (N2 o u1) (D2 o h1))
<- cut (? A1) D1’ ([h] ?L ([o][h1] D2 o h1) h) D.
% Hybrid
- : nndseq (attI N) (atR D)
<- nndseq N D.
- : nndseq (attE N1 ([u] N2 u)) D
<- nndseq N1 D1’
<- ({u}{h} nndseq u (init h) -> nndseq (N2 u) (D2 h))
<- cut (A at W) D1’ ([h] atL ([h’] D2 h’) h) D.
% Conjunction
- : nndseq (&&I N1 N2) (&R D1 D2)
<- nndseq N1 D1
<- nndseq N2 D2.
- : nndseq (&&E1 (N : A & B @@ W)) F
<- nndseq N D
<- cut (A & B) D ([h] &L ([ha][hb] init ha) h) F.
- : nndseq (&&E2 (N : A & B @@ W)) F
<- nndseq N D
<- cut (A & B) D ([h] &L ([ha][hb] init hb) h) F.
242
block {u:A @@ W} {h:hyp A W} {r:nndseq u (init h)}.
% Init
- : sseqnd (init H) N <- hhypnd H N.
% Necessity
- : sseqnd (!R ([o] D1 o)) (!!I ([o] N1 o))
<- ({o:world} sseqnd (D1 o) (N1 o)).
- : sseqnd (!L ([h1] D2 h1) H) (N2 (!!E N))
<- ({h1:hyp A1 W’} {u1:A1 @@ W’}
hhypnd h1 u1 -> sseqnd (D2 h1) (N2 u1))
<- hhypnd H N.
% Possibility
- : sseqnd (?R D1) (??I N1)
<- sseqnd D1 N1.
- : sseqnd (?L ([o][h1] D2 o h1) H)
(??E N ([o] [u1] N2 o u1))
<- ({o:world} {h1:hyp A1 o} {u1:A1 @@ o}
hhypnd h1 u1 -> sseqnd (D2 o h1) (N2 o u1))
<- hhypnd H N.
% Hybrid
- : sseqnd (atR D) (attI N) <- sseqnd D N.
- : sseqnd (atL ([h] D h) H)
(attE N1 ([u] N2 u))
<- ({h : hyp A W}{u : A @@ W}
hhypnd h u -> sseqnd (D h) (N2 u))
<- hhypnd H N1.
% Conjunction
- : sseqnd (&R D1 D2) (&&I N1 N2)
<- sseqnd D1 N1
<- sseqnd D2 N2.
- : sseqnd (&L ([ha][hb] D ha hb) H) (N (&&E1 Nab) (&&E2 Nab))
<- ({ha : hyp A W}{ua : A @@ W}{t : hhypnd ha ua}
{hb : hyp B W}{ub : B @@ W}{t : hhypnd hb ub}
sseqnd (D ha hb) (N ua ub))
<- hhypnd H Nab.
% Implication
- : sseqnd (=>R ([h1] D2 h1)) (==>I ([u1] N2 u1))
<- ({h1:hyp A1 W} {u1:A1 @@ W} hhypnd h1 u1 ->
sseqnd (D2 h1) (N2 u1)).
- : sseqnd (=>L D1 ([h2] D2 h2) H) (N2 (==>E N N1))
<- sseqnd D1 N1
<- ({h2:hyp A2 W} {u2:A2 @@ W} hhypnd h2 u2 ->
sseqnd (D2 h2) (N2 u2))
<- hhypnd H N.
% Disjunction
- : sseqnd (|R1 D) (||I1 N) <- sseqnd D N.
- : sseqnd (|R2 D) (||I2 N) <- sseqnd D N.
243
<- ({hb}{b}{_ : hhypnd hb b} sseqnd (Db hb) (Nb b))
<- hhypnd H Nab.
% Propositions
% Natural deduction
244
!mob : mobile (! A).
?mob : mobile (? A).
atmob : mobile (A at W).
&mob : mobile A -> mobile B -> mobile (A & B).
|mob : mobile A -> mobile B -> mobile (A | B).
% Implication
lam : (val A W -> B @ W) -> (A => B @ W).
app : (A => B @ W) -> A @ W -> B @ W.
% Necessity
box : ({o:world} A @ o) -> ! A @ W.
unbox : ! A @ W -> A @ W.
% Possibility
here : A @ W -> ? A @ W.
letd : ? A @ W -> ({o:world} val A o -> C @ W) -> C @ W.
% Hybrid
hold : A @ W -> A at W @ W.
leta : A at W’ @ W -> (val A W’ -> C @ W) -> C @ W.
% Conjunction
, : A @ W -> B @ W -> A & B @ W. %infix none 5 ,.
#1 : A & B @ W -> A @ W.
#2 : A & B @ W -> B @ W.
% Disjunction
inl : A @ W -> A | B @ W.
inr : B @ W -> A | B @ W.
case : A | B @ W -> (val A W -> C @ W) -> (val B W -> C @ W) -> C @ W.
245
s-pair : vshift (&mob Ma Mb) (vpair Va Vb) (vpair Va’ Vb’)
<- vshift Ma Va Va’
<- vshift Mb Vb Vb’.
246
world : type. %name world W w.
prop : type. %name prop A.
% Structural Rules
% Implication
=>I : (A @ W -> B @ W) -> (A => B @ W).
=>E : (A => B @ W) -> A @ W -> B @ W.
% Hybrid
atI : A @ W -> (A at W @ W).
atE : A at W’ @ W -> (A @ W’ -> C @ W’’) -> C @ W’’.
% Necessity
!I : ({o:world} A @ o) -> ! A @ W.
!E : ! A @ W -> A @ W.
!G : ! A @ W’ -> ! A @ W = [m] get !mob m.
% Possibility
?I : A @ W -> ? A @ W.
?E : ? A @ W -> ({o:world} A @ o -> C @ W) -> C @ W.
?G : ? A @ W’ -> ? A @ W = [m] get ?mob m.
% Conjunction
&I : A @ W -> B @ W -> (A & B @ W).
&E1 : (A & B @ W) -> A @ W.
&E2 : (A & B @ W) -> B @ W.
% Falsehood
_|_ : prop.
_|_E : _|_ @ W -> C @ W’.
# : type.
247
% judgmental
contra : true A W -> false A W -> #.
% hybrid
atT : (true A W’ -> #) ->
(true (A at W’) W -> #).
atF : (false A W’ -> #) ->
(false (A at W’) W -> #).
% arrow
=>T : (false A W -> #) -> (true B W -> #) ->
(true (A => B) W -> #).
=>F : (true A W -> false B W -> #) ->
(false (A => B) W -> #).
% box
!T : (true A W’ -> #) ->
(true (! A) W -> #).
!F : ({w:world} false A w -> #) ->
(false (! A) W -> #).
% dia
?T : ({w:world} true A w -> #) ->
(true (? A) W -> #).
?F : (false A W’ -> #) ->
(false (? A) W -> #).
% conjunction
&T : (true A W -> true B W -> #) ->
(true (A & B) W -> #).
&F : (false A W -> #) -> (false B W -> #) ->
(false (A & B) W -> #).
% falsehood
xm : {A:prop} {W:world} (true A W -> #) -> (false A W -> #) -> # -> type.
%mode xm +A +W +D +E -F.
% initial cuts
xt_init : xm A W ([xt] contra xt D) ([xf] E xf) (E D).
xf_init : xm A W ([xt] D xt) ([xf] contra E xf) (D E).
% unused assumptions
xt_unused : xm A W ([x1] contra DT DF) E (contra DT DF).
xf_unused : xm A W D ([x1] contra ET EF) (contra ET EF).
% commutative cases.
% note that we need to consider each rule in both D and E, so
% there are four cases for each connective.
% implication
xd_ct=> : xm A W ([a : true A W] =>T
([cf : false C W’] D1 a cf)
([dt : true D W’] D2 a dt) DD) ([af] E af)
(=>T ([cf : false C W’] F1 cf) ([dt : true D W’] F2 dt) DD)
<- ({cf : false C W’} xm A W ([a] D1 a cf) E (F1 cf))
<- ({dt : true D W’} xm A W ([a] D2 a dt) E (F2 dt)).
248
xe_ct=> : xm A W ([a] DD a) ([af : false A W] =>T
([cf : false C W’] E1 af cf)
([dt : true D W’] E2 af dt) EE)
(=>T ([cf] F1 cf) ([dt] F2 dt) EE)
<- ({cf : false C W’} xm A W DD ([af] E1 af cf) (F1 cf))
<- ({dt : true D W’} xm A W DD ([af] E2 af dt) (F2 dt)).
% box
xd_ct! : xm A W ([a : true A W] !T ([bt : true B W’] D1 a bt) DD)
([af] E af)
(!T ([bt : true B W’] F1 bt) DD)
<- ({bt : true B W’} xm A W ([a] D1 a bt) E (F1 bt)).
xe_ct! : xm A W ([a] D a)
([af : false A W] !T ([bt : true B W’] E1 af bt) EE)
(!T ([bt : true B W’] F1 bt) EE)
<- ({bt : true B W’} xm A W D ([af] E1 af bt) (F1 bt)).
xe_cf! : xm A W ([a] D a)
([af] !F ([w][bf : false B w] E1 af w bf) EE)
(!F ([w][bf] F1 w bf) EE)
<- ({w}{bf} xm A W D ([af] E1 af w bf) (F1 w bf)).
% dia
xd_ct? : xm A W ([a : true A W] ?T ([w][bt] D1 a w bt) DD)
([af] E af)
(?T ([w][bt] F1 w bt) DD)
<- ({w}{bt} xm A W ([a] D1 a w bt) ([af] E af) (F1 w bt)).
xe_ct? : xm A W ([a] D a)
([af] ?T ([w][bt] E1 af w bt) EE)
(?T ([w][bt] F1 w bt) EE)
<- ({w}{bt} xm A W ([a] D a) ([af] E1 af w bt) (F1 w bt)).
xe_cf? : xm A W ([a] D a)
([af] ?F ([bf : false B W’] E1 af bf) EE)
(?F ([bf] F1 bf) EE)
<- ({bf} xm A W ([a] D a) ([af] E1 af bf) (F1 bf)).
% hybrid
249
xd_ctat : xm A W ([a] atT ([bt] D a bt) DD)
([af] E af)
(atT ([bt] F bt) DD)
<- ({bt} xm A W ([a] D a bt) ([af] E af) (F bt)).
xe_ctat : xm A W ([a] D a)
([af] atT ([bt] E af bt) EE)
(atT ([bt] F bt) EE)
<- ({bt} xm A W D ([af] E af bt) (F bt)).
xd_cfat : xm A W ([a] atF ([bf] D a bf) DD) E
(atF ([bf] F bf) DD)
<- ({bf} xm A W ([a] D a bf) E (F bf)).
xe_cfat : xm A W D ([af] atF ([bf] E af bf) EE)
(atF ([bf] F bf) EE)
<- ({bf} xm A W D ([af] E af bf) (F bf)).
% conjunction
xd_ct& : xm A W ([a] &T ([ct][dt] D1 a ct dt) DD)
([af] E af)
(&T ([ct][dt] F1 ct dt) DD)
<- ({ct}{dt} xm A W ([a] D1 a ct dt) ([af] E af) (F1 ct dt)).
xe_ct& : xm A W ([a] D a)
([af] &T ([ct][dt] E1 af ct dt) EE)
(&T ([ct][dt] F1 ct dt) EE)
<- ({ct}{dt} xm A W ([a] D a) ([af] E1 af ct dt) (F1 ct dt)).
xe_cf& : xm A W ([a] D a)
([af] &F ([cf] E1 af cf) ([df] E2 af df) EE)
(&F ([cf] F1 cf) ([df] F2 df) EE)
<- ({cf} xm A W ([a] D a) ([af] E1 af cf) (F1 cf))
<- ({df} xm A W ([a] D a) ([af] E2 af df) (F2 df)).
% implication
x_=> : xm (A => B) W
([it : true (A => B) W] =>T ([af : false A W] D1 it af)
([bt : true B W] D2 it bt) it)
([if : false (A => B) W] =>F ([a : true A W]
[bf : false B W] E1 if a bf) if)
F
<- ({a : true A W} {bf : false B W}
xm (A => B) W ([it] =>T ([af : false A W] D1 it af)
([bt] D2 it bt) it)
([if] E1 if a bf) (E1’ a bf))
<- ({af : false A W}
xm (A => B) W ([it] D1 it af)
([if] =>F ([a][bf] E1 if a bf) if) (D1’ af))
<- ({bt : true B W}
xm (A => B) W ([it] D2 it bt)
([if] =>F ([a][bf] E1 if a bf) if) (D2’ bt))
<- ({bf : false B W}
xm A W ([a : true A W] E1’ a bf)
([af : false A W] D1’ af) (F’ bf))
<- xm B W ([bt] D2’ bt) ([bf] F’ bf) F.
% box
250
x_! : xm (! A) W ([nt] !T ([a : true A W’] D1 nt a) nt)
([nf] !F ([w][af : false A w] E1 nf w af) nf)
F
<- ({w : world}{af : false A w}
xm (! A) W ([nt] !T ([a] D1 nt a) nt)
([nf] E1 nf w af) (E1’ w af))
<- ({a : true A W’}
xm (! A) W ([nt] D1 nt a)
([nf] !F ([w][af] E1 nf w af) nf) (D1’ a))
<- xm A W’ ([a : true A W’] D1’ a)
([af : false A W’] E1’ W’ af) F.
% dia
x_? : xm (? A) W ([nt] ?T ([w][a] D1 nt w a) nt)
([nf] ?F ([af] E1 nf af) nf)
F
<- ({w : world}{a : true A w}
xm (? A) W ([nt] D1 nt w a)
([nf] ?F ([af] E1 nf af) nf) (D1’ w a))
<- ({af : false A W’}
xm (? A) W ([nt] ?T ([w][a] D1 nt w a) nt)
([nf] E1 nf af) (E1’ af))
<- xm A W’ ([a : true A W’] D1’ W’ a) ([af] E1’ af) F.
% conjunction
x_& : xm (A & B) W ([&t] &T ([a][bt] D1 &t a bt) &t)
([&f] &F ([af] E1 &f af) ([bf] E2 &f bf) &f)
F
<- ({a}{bt} xm (A & B) W ([&t] D1 &t a bt)
([&f] &F ([af] E1 &f af)
([bf] E2 &f bf) &f)
(D1’ a bt))
<- ({af} xm (A & B) W ([&t] &T ([a][bt] D1 &t a bt) &t)
([&f] E1 &f af)
(E1’ af))
<- ({bf} xm (A & B) W ([&t] &T ([a][bt] D1 &t a bt) &t)
([&f] E2 &f bf)
(E2’ bf))
<- ({bt} xm A W ([a] D1’ a bt) ([af] E1’ af) (D1’’ bt))
<- (xm B W ([bt] D1’’ bt) ([bf] E2’ bf) F).
switch : mobile A -> (false A W -> #) -> {W’:world} (false A W’ -> #) -> type.
%mode switch +M +FW +W’ -FW’.
- : switch !mob F1 _ F
<- ({!f} xm (! A) W’
([!t’ : true (! A) W’]
251
!F ([w’’ : world][af’’ : false A w’’]
(!T ([at’’ : true A w’’] contra at’’ af’’) !t’)) !f)
([!f’ : false (! A) W’] F1 !f’)
(F !f)).
- : switch ?mob F1 _ F
<- ({?f : false (? A) W}
xm (? A) W’
([?t’] ?T ([w’’][at’’] ?F ([af’’] contra at’’ af’’) ?f) ?t’)
([?f’] F1 ?f’)
(F ?f)).
- : switch atmob F1 _ F
<- ({atf}
xm (A at _) W’
([t’] atT ([at’’] atF ([af’’] contra at’’ af’’) atf) t’)
([f’] F1 f’)
(F atf)).
% get
ns-!G : ndseq (get M (D : A @ W’)) ([f: false A W] F f)
<- ndseq D ([!f’] F1 !f’)
<- switch M F1 W F.
% hybrid
ns-atI : ndseq (atI N) (atF F) <- ndseq N F.
ns-atE : ndseq (atE D1 ([a] D2 a))
([cf] F cf)
<- ndseq D1 F1
<- ({aa}{a}
ndseq aa ([af] contra a af) ->
ndseq (D2 aa) ([cf] F2 a cf))
<- ({cf}
xm (A at W’) W ([t] atT ([a] F2 a cf) t)
([f] F1 f) (F cf)).
% conjunction
ns-&I : ndseq (&I D1 D2) (&F F1 F2)
<- ndseq D1 F1
<- ndseq D2 F2.
252
([&f] F2 &f) (F bf)).
% falsehood
% implication
% box
% dia
253
<- ndseq D ([af] F af)
<- contfalse K AF.
% if G,x:A@W; D |- M : *
% and G; D,u:A@W |- N : B
% then G; D |- [[ x.M/u ]] N : B
xs-closed : xs D ([a*] E) E.
% falsehood
xs-_|_E : xs D ([u] _|_E (EE u)) (_|_E EE’)
<- xs D EE EE’.
xs-atI : xs D ([u] atI (E u)) (atI F) <- xs D E F.
xs-atE : xs D ([u] (atE (E1 u) ([a] E2 a u))) (atE F1 F2)
<- xs D E1 F1
<- ({a’} xs D (E2 a’) (F2 a’)).
% conjunction
xs-&I : xs D ([u] &I (EA u) (EB u)) (&I F1 F2)
<- xs D EA F1
<- xs D EB F2.
xs-&E1 : xs D ([u] &E1 (E u)) (&E1 F) <- xs D E F.
xs-&E2 : xs D ([u] &E2 (E u)) (&E2 F) <- xs D E F.
% implication
xs-=>I : xs D ([u] =>I ([aw : A @ W] E aw u)) (=>I ([aw] F aw))
<- ({aw : A @ W} xs D (E aw) (F aw)).
xs-=>E : xs D ([u] =>E (DF u) (DA u)) (=>E FF FA)
<- xs D DF FF
<- xs D DA FA.
% box
xs-!I : xs D ([u : A * W] !I ([w] E u w)) (!I ([w] F w))
<- ({w : world} xs D ([u] E u w) (F w)).
xs-!G : xs D ([u] !G (E u)) (!G F) <- xs D E F.
254
xs-!E : xs D ([u] !E (E u)) (!E F) <- xs D E F.
% possibility
xs-?E : xs D ([u] (?E (E1 u) ([w][a] E2 w a u))) (?E F1 ([w][a] F2 w a))
<- xs D E1 F1
<- ({w:world}{aw: A @ w}
xs D (E2 w aw) (F2 w aw)).
xs-?I : xs D ([u] ?I (E u)) (?I F) <- xs D E F.
xs-?G : xs D ([u] ?G (E u)) (?G F) <- xs D E F.
% G # D then G ; D |- M : *
seqnd : # -> ({a}{w} a @ w) -> type. %name seqnd S.
truend : true A W -> A @ W -> type. %name truend T t.
falsend : false A W -> A * W -> type. %name falsend F f.
%mode seqnd +D -F.
%mode truend +D -F.
%mode falsend +D -F.
% judgmental
sn-contra : seqnd (contra AT AF) ([c : prop][w : world] throw A AC)
<- truend AT A
<- falsend AF AC.
% falsehood
sn-_|_T : seqnd (_|_T FT) ([c][w] _|_E FT’) <- truend FT FT’.
% hybrid
sn-atT : seqnd (atT ([aa] D aa) Tat)
([c][w] atE (get atmob Nat : _ @ W’) ([a’] F a’ c w))
<- truend Tat Nat
<- ({aa : true A W’}{a’ : A @ W’}
truend aa a’ ->
seqnd (D aa) ([c][w] F a’ c w)).
sn-atF : seqnd (atF ([af’] D af’) Fat) FF
<- falsend Fat Nat
<- ({af’ : false A W’}{a*’ : A * W’}
falsend af’ a*’ ->
seqnd (D af’) ([c][w] F1 a*’ c w))
<- ({cc}{ww}
xs ([c][w] [a’] (throw (get atmob (atI a’)) Nat))
([a*’] F1 a*’ cc ww)
(FF cc ww)).
% conjunction
sn-&T : seqnd (&T ([a][bt] D a bt) T&) ([c][w] F (&E1 N&) (&E2 N&) c w)
<- ({aa}{a}
255
truend aa a ->
{bb}{b}
truend bb b ->
seqnd (D aa bb) ([c][w] F a b c w))
<- truend T& N&.
% implication
% actually, implication is not just the elim rule, because
% classical implication is phrased differently.
sn-=>T : seqnd (=>T ([af : false A W] D1 af) ([bt] D2 bt)
(T=> : true (A => B) W))
FF
<- truend T=> N=>
<- ({af}{a*} falsend af a* ->
seqnd (D1 af) ([c][w] F1 a* c w))
<- ({bt}{b}
truend bt b ->
seqnd (D2 bt) ([c][w] F2 b c w))
<- ({cc}{ww}
xs ([c][w] [a] (F2 (=>E N=> a) c w))
([a*] F1 a* cc ww)
(FF cc ww)).
% letcc-free version
% u : A=>B |- throw \x:A . [[ c:B. throw \unused:A.c to u / ... ]](IH) to u
sn-=>F : seqnd (=>F ([aa : true A W][bf : false B W] D aa bf) F=>)
([c][w] throw (=>I [a] FZ a B W) N=> )
<- falsend F=> N=>
<- ({aa}{a}
truend aa a ->
{bf}{b*} falsend bf b* ->
seqnd (D aa bf) ([c][w] F1 a b* c w))
<- ({a : A @ W}
{cc}{ww}
xs ([c][w] [b] throw (=>I [a-unused : A @ W] b) N=>)
([b*] F1 a b* cc ww)
(FZ a cc ww)).
256
% box
sn-!T : seqnd (!T ([aa’ : true A W’] D aa’) T!) ([c][w] F (!E (!G N!)) c w)
<- ({aa’}{a’}
truend aa’ a’ ->
seqnd (D aa’) ([c][w] F a’ c w))
<- truend T! N!.
% dia
% x : ?A@w |- let dia <y,w’> = get<w>x in IH end
sn-?T : seqnd (?T ([w’][aa] D w’ aa) T?)
([c][w] ?E (?G N?) ([w’][a’] F w’ a’ c w))
<- truend T? N?
<- ({w’}{aa : true A w’}{a’ : A @ w’}
truend aa a’ ->
seqnd (D w’ aa) ([c][w] F w’ a’ c w)).
% for xs
%block blocku : some {A : prop} {W : world}
block {u : A * W}.
%block blocka : some {A : prop} {W : world}
block {u : A @ W}.
% for seqnd
%block blocknt : some {A:prop} {W:world}
block {aa:true A W} {a:A @ W} {t:truend aa a}.
%block blocknf : some {A:prop} {W:world}
block {af:false A W} {a:A * W} {t:falsend af a}.
257
A.5.1 Natural numbers
nat : type. %name nat N n.
0 : nat.
s : nat -> nat.
% if a + (b + 1) = c then (a + 1) + b = c
plus-flip : plus X (s Y) Z -> plus (s X) Y Z -> type.
%mode plus-flip +P -PP.
plus-flip0 : plus-flip (ps p0 : plus X (s 0) (s X)) p0.
258
plus-flips : plus-flip (ps (ps P)) (ps PP) <- plus-flip (ps P) PP.
%%%%
%%%% Types.
%%%%
%%%%
259
%%%% Syntax of expressions.
%%%%
% structural
get : world -> exp -> exp.
% products
pair : exp -> exp -> exp.
fst : exp -> exp.
snd : exp -> exp.
% implication
app : exp -> exp -> exp.
lam : (exp -> exp) -> exp.
% box
box : (world -> exp) -> exp.
unbox : exp -> exp.
% dia
here : exp -> exp.
letdia : exp -> (world -> exp -> exp) -> exp.
% falsehood
abort : exp -> exp.
% classical stuff
throw : exp -> cexp -> exp.
letcc : (cexp -> exp) -> exp.
% hybrid
hold : exp -> exp.
leta : exp -> (exp -> exp) -> exp.
% proofs of value-ness
value : exp -> type.
%%%%
%%%% Syntax of continuations.
%%%%
260
freturn : nat -> nat -> cont.
fletdia : cont -> (world -> exp -> exp) -> cont.
fpair1 : cont -> exp -> cont. % k |> ([], M)
fpair2 : cont -> {V : exp} value V -> cont. % k |> (v, [])
fhere : cont -> cont.
fapp1 : cont -> exp -> cont. % k |> ([] M)
fapp2 : cont -> {V : exp} value V -> cont. % k |> (v [])
fhold : cont -> cont.
fleta : cont -> (exp -> exp) -> cont.
%%%%
%%%% Networks.
%%%%
% mode of evaluation
mode : type.
eval : exp -> mode.
ret : {V : exp} value V -> mode.
%%%%
%%%% Store (configuration) typing.
%%%%
%%%%
261
%%%% Typing labels
%%%%
%%%%
%%%% Typing of worlds.
%%%%
%%%%
%%%% Typing of continuation expressions.
%%%%
welltyped_cexp : wslist X -> cexp -> typ -> world -> type.
%%%%
%%%% Typing of expressions.
%%%%
welltyped_exp : wslist X -> exp -> typ -> world -> type.
wte_pair : welltyped_exp S M A W ->
262
welltyped_exp S N B W ->
welltyped_exp S (pair M N) (A & B) W.
263
welltyped_exp (S : wslist N) (lab L) A (wconst W).
%%%%
%%%% Typing of continuations.
%%%%
welltyped_cont : wslist N -> cont -> typ -> world -> type.
%name welltyped_cont WTC wtc.
wtc_finish : welltyped_cont _ finish _ _.
wtc_fabort : welltyped_cont S fabort _|_ W.
wtc_freturn : clabtype_is S L A J ->
mobile A ->
welltyped_cont S (freturn J L) A W.
264
% worlds
%%%%
%%%% Typing of network bits.
%%%%
welltyped_mode : wslist N -> mode -> typ -> world -> type.
wt_eval : welltyped_exp S M A W ->
welltyped_mode S (eval M) A W.
wt_ret : welltyped_exp S M A W ->
welltyped_mode S (ret M _) A W.
% ensure that a continuation table has the given type (at the supplied world)
% under the given store type.
welltyped_conts : wslist N -> typetable M -> clist M -> world -> type.
wtcs_empty : welltyped_conts S ttnil cnil W.
wtcs_one : welltyped_conts S TTL CTL W ->
welltyped_cont S K A W ->
welltyped_conts S (tt// A TTL) (c// K CTL) W.
265
welltyped_conts S CT CD (wconst N-1) ->
welltyped_datas S BT BD (wconst N-1) ->
welltyped_worldsr (s N-1) S (ws// CT BT STL) (wd// CD BD WTL).
%%%%
%%%% Typing of networks.
%%%%
%%%%
%%%% Lookup of labels.
%%%%
lookinc : clist N -> nat -> cont -> type. %name lookinc LIC lic.
lookinc_next : lookinc (c// _ VTL) (s OFF) K
266
<- lookinc VTL OFF K.
% lookupv W J L V
% ensure that in config W at world constant J, label L maps to some value V.
lookupv : wdlist N -> nat -> nat -> {V : exp} value V -> type.
lookupv_next : lookupv (wd// _ BD BTL) (s X) L V VP
<- lookupv BTL X L V VP.
lookupv_found :
minus SIZE-1 L OFF ->
lookinv BD OFF V VP ->
lookupv (wd// _ (BD : vlist (s SIZE-1)) _) 0 L V VP.
% add_value W J V VAL W’ L
% adding value V to the world J within W results in worlds
% W’ and new label L.
add_value : wdlist N -> nat -> {V : exp} value V -> wdlist N -> nat -> type.
%mode add_value +W +J +V +VP -W’ -L.
addv_next : add_value (wd// CD BD BTL) (s X) V VP (wd// CD BD BTL’) L
<- add_value BTL X V VP BTL’ L.
% adds to the head of the list, which gets index n (the current length)
addv_found :
{BD : vlist N}
add_value (wd// CD BD BTL) 0 V VP (wd// CD (v// V VP BD) BTL) N.
lookupc_found :
minus SIZE-1 L OFF ->
lookinc CD OFF K ->
lookupc (wd// (CD : clist (s SIZE-1)) _ _) 0 L K.
add_cont : wdlist N -> nat -> cont -> wdlist N -> nat -> type.
%mode add_cont +W +J +K -W’ -L.
%%%%
%%%% Stepping relation.
%%%%
267
step_pfst : net W J K (eval (fst M)) |-> net W J (ffst K) (eval M).
step_psnd : net W J K (eval (snd M)) |-> net W J (fsnd K) (eval M).
step_rfst : net W J (ffst K) (ret (pair V _) (valpair VP _)) |-> net W J K (ret V VP).
step_rsnd : net W J (fsnd K) (ret (pair _ V) (valpair _ VP)) |-> net W J K (ret V VP).
step_ppair : net W J K (eval (pair M N)) |-> net W J (fpair1 K N) (eval M).
step_fpair : net W J (fpair1 K N) (ret V VP) |-> net W J (fpair2 K V VP) (eval N).
step_rpair : net W J (fpair2 K V VP) (ret VV VVP) |->
net W J K (ret (pair V VV) (valpair VP VVP)).
step_phere : net W J K (eval (here M)) |-> net W J (fhere K) (eval M).
step_rhere : add_value W J V VP W’ L ->
net W J (fhere K) (ret V VP) |-> net W’ J K (ret (addr (wconst J) L) valaddr).
step_phold : net W J K (eval (hold M)) |-> net W J (fhold K) (eval M).
step_rhold : net W J (fhold K) (ret V VP) |-> net W J K (ret (held V) (valheld VP)).
step_papp : net W J K (eval (app M N)) |-> net W J (fapp1 K N) (eval M).
step_fapp : net W J (fapp1 K N) (ret V VP) |-> net W J (fapp2 K V VP) (eval N).
step_rapp : net W J (fapp2 K (lam F) _) (ret V _) |-> net W J K (eval (F V)).
%%%%
%%%% Lemmas.
%%%%
wlti-next :
weaken-lti SL SL’ E LTI LTI’ ->
weaken-lti (ws// CT BT SL) (ws// CT’ BT’ SL’) (ex-one E _ _)
(lti_next LTI) (lti_next LTI’).
wlti-foundsame :
weaken-lti _ _ (ex-one _ _ extendst-same)
(lti_found MINUS TTS) (lti_found MINUS TTS).
wlti-foundcons :
268
minus-sfirst MINUS MINUS’ ->
weaken-lti (ws// _ (BT : typetable (s SIZE-1)) _)
(ws// _ (tt// A BT) _) (ex-one _ _ extendst-cons)
(lti_found MINUS TTS) (lti_found MINUS’ (tts_next TTS)).
wclti-next :
weaken-clti SL SL’ E CLTI CLTI’ ->
weaken-clti (ws// CT BT SL) (ws// CT’ BT’ SL’) (ex-one E _ _)
(clti_next CLTI) (clti_next CLTI’).
wclti-foundsame :
weaken-clti _ _ (ex-one _ extendst-same _)
(clti_found MINUS TTS) (clti_found MINUS TTS).
wclti-foundcons :
minus-sfirst MINUS MINUS’ ->
weaken-clti (ws// (CT : typetable (s SIZE-1)) _ _)
(ws// (tt// A CT) _ _) (ex-one _ extendst-cons _)
(clti_found MINUS TTS) (clti_found MINUS’ (tts_next TTS)).
%% worlds
weaken-wtw : extends S S’ ->
welltyped_world S W ->
welltyped_world S’ W -> type.
%mode weaken-wtw +E +WT -WT’.
%% continuation expressions
weaken-wtce : extends S S’ ->
welltyped_cexp S U A W ->
welltyped_cexp S’ U A W -> type.
%mode weaken-wtce +E +WT -WT’.
%% expressions
weaken-wte : extends S S’ ->
welltyped_exp S M A W ->
welltyped_exp S’ M A W -> type.
%mode weaken-wte +E +WT -WT’.
wwte-throw :
weaken-wtce EX CW CW’ ->
weaken-wte EX W W’ ->
weaken-wte EX (wte_throw CW W) (wte_throw CW’ W’).
269
wwte-app : weaken-wte EX W1 W1’ ->
weaken-wte EX W2 W2’ ->
weaken-wte EX (wte_app W1 W2) (wte_app W1’ W2’).
wwte-box :
({w : world} {wt : {X : nat} {S : wslist X}
welltyped_world S w}
{thm : {Y : nat} {s : wslist Y} {s’ : wslist Y}
{EX : extends s s’}
weaken-wtw EX (wt Y s) (wt Y s’)}
weaken-wte EX (WT w wt) (WT’ w wt)) ->
weaken-wte EX (wte_box WT) (wte_box WT’).
wwte-leta :
weaken-wte EX M M’ ->
( {x : exp}
{wt : {X : nat} {S : wslist X} welltyped_exp S x A W’}
{thm : {Y : nat} {s : wslist Y} {s’ : wslist Y}
{EX : extends s s’}
weaken-wte EX (wt Y s) (wt Y s’)}
weaken-wte EX (N x wt) (N’ x wt)) ->
weaken-wte EX (wte_leta M ([x][wt] N x wt))
(wte_leta M’ ([x][wt] N’ x wt)).
wwte-letcc:
({u :cexp}
{w :{X : nat} {S : wslist X} welltyped_cexp S u A W}
{thm: {Y : nat} {s : wslist Y} {s’ : wslist Y}
{EX : extends s s’}
weaken-wtce EX (w Y s) (w Y s’)}
weaken-wte EX (M u w) (M’ u w)) ->
weaken-wte EX (wte_letcc ([u][wt] M u wt))
270
(wte_letcc ([u][wt] M’ u wt)).
271
welltyped_exp SS x A W’}
{thm : {Y : nat} {s : wslist Y} {s’ : wslist Y}
{EX : extends s s’}
weaken-wte EX (wt Y s) (wt Y s’)}
weaken-wte E (M x wt) (M’ x wt)) ->
weaken-wtc E (wtc_leta K M) (wtc_leta K’ M’).
%block bm :
some {A : typ} {J : nat}
block {m:exp}
{wt : {X : nat} {SS : wslist X} welltyped_exp SS m A (wconst J)}
{thm : {Y : nat} {s : wslist Y} {s’ : wslist Y}
{ex : extends s s’} weaken-wte ex (wt Y s) (wt Y s’)}.
%block bx :
some {A : typ} {W : world}
block {x : exp}
{w : {X : nat} {S : wslist X} welltyped_exp S x A W}
{thm : {Y : nat} {s : wslist Y} {s’ : wslist Y}
{EX : extends s s’} weaken-wte EX (w Y s) (w Y s’)}.
%block bu :
some {A : typ} {W : world}
block {u : cexp}
{w : {X : nat} {S : wslist X} welltyped_cexp S u A W}
{thm : {Y : nat} {s : wslist Y} {s’ : wslist Y}
{EX : extends s s’} weaken-wtce EX (w Y s) (w Y s’)}.
%block bww :
block {w : world}
{wtw : {X : nat}{S : wslist X} welltyped_world S w }
{thm : {Y : nat} {s : wslist Y} {s’ : wslist Y}
{EX : extends s s’} weaken-wtw EX (wtw Y s) (wtw Y s’)}.
272
% substitution for magic world hypotheses in expression typing
% derivations. Sticks in the constant world J; all we need to
% know is that it’s in the domain of the store type (J < SN).
% uses var
wtews-getv : wte-wsubst J LT ([wt] WT wt) WT’ ->
wte-wsubst J LT ([wt : {X : nat} {SS : wslist X}
welltyped_world SS (wconst J)]
wte_get (wt _ S) (WT wt) MOB)
(wte_get (wtw_const S LT) WT’ MOB).
wtews-unbox :
wte-wsubst J LT ([wt] WT wt) WT’ ->
wte-wsubst J LT ([wt] wte_unbox (WT wt)) (wte_unbox WT’).
273
{thm : {Y : nat} {s : wslist Y} {s’ : wslist Y}
{EX : extends s s’}
weaken-wtw EX (wtw Y s) (wtw Y s’)}
wte-wsubst J LT ([w] W w wo wtw) (W’ wo wtw)) ->
wte-wsubst J LT ([w] wte_box (W w)) (wte_box W’).
wtews-letdia :
wte-wsubst J LT ([wt] M wt) M’ ->
% essentially same as lambda case + box case
({wo : world}
{wtw : {X : nat}{S : wslist X}
welltyped_world S wo }
{thm : {Y : nat} {s : wslist Y} {s’ : wslist Y}
{EX : extends s s’}
weaken-wtw EX (wtw Y s) (wtw Y s’)}
wtews-leta :
wte-wsubst J LT ([wt] M wt) M’ ->
({xx : exp} {wtt : {X : nat} {SS : wslist X}
welltyped_exp SS xx A’ W’}
{thm :
{X : nat} {SS : wslist X} {JJ : nat} {LLT : JJ < X}
wte-wsubst JJ LLT ([_] wtt X SS) (wtt X SS)}
wte-wsubst J LT ([wt] N xx wtt wt)
(N’ xx wtt)) ->
wte-wsubst J LT ([wt] wte_leta (M wt)
([xx : exp][wtt]
N xx wtt wt))
(wte_leta M’ ([xx][wtt] N’ xx wtt)).
274
<- val-csubst VB C VB’.
- : val-csubst ([u] (valheld (VH u))) C (valheld VH’)
<- val-csubst VH C VH’.
275
wte-csubst WTA ([u][w] wte_lam ([xx][ww] WW xx ww u w))
(wte_lam ([xx][ww] WW’ xx ww)).
wtecs-letdia :
wte-csubst WTA ([x][wt] M x wt) M’ ->
% essentially same as lambda case + box case
({ww : world}
{wwt : {X : nat} {SS : wslist X}
welltyped_world SS ww}
{xx : exp} {wtt : {X : nat} {SS : wslist X}
welltyped_exp SS xx A’ ww}
{thm : {X : nat} {SS : wslist X}
{B : typ} {WO : world} {U : cexp}
{WTN : welltyped_cexp SS U B WO}
wte-csubst WTN ([_][_] wtt X SS) (wtt X SS)}
wte-csubst WTA ([u][w] N ww wwt xx wtt u w)
(N’ ww wwt xx wtt)) ->
wte-csubst WTA ([u][wt] wte_letdia (M u wt)
([ww : world][wwt][xx : exp][wtt]
N ww wwt xx wtt u wt))
(wte_letdia M’ ([ww][wwt][xx][wtt] N’ ww wwt xx wtt)).
276
wtes-heldX : val-subst VAL C VAL’ ->
wte-subst WTA ([x][w] WW x w) WW’ ->
wte-subst (WTA : welltyped_exp _ C _ _)
([x][w] wte_held (WW x w) (VAL x)) (wte_held WW’ VAL’).
wtes-throw : wte-subst WTA ([x][w] WT x w) WT’ ->
wte-subst WTA ([x][w] wte_throw WC (WT x w)) (wte_throw WC WT’).
wtes-abort : wte-subst WTA WT WT’ ->
wte-subst WTA ([x][w] wte_abort (WT x w)) (wte_abort WT’).
wtes-get : wte-subst WTA ([x][w] WT x w) WT’ ->
wte-subst WTA ([x][w] wte_get WW (WT x w) MOB) (wte_get WW WT’ MOB).
wtes-unbox : wte-subst WTA ([x][w] WT x w) WT’ ->
wte-subst WTA ([x][w] wte_unbox (WT x w)) (wte_unbox WT’).
wtes-app : wte-subst WTA ([x][w] WF x w) WF’ ->
wte-subst WTA ([x][w] WB x w) WB’ ->
wte-subst WTA ([x : exp]
[w : {X : nat} {SS : wslist X}
welltyped_exp SS x A WA]
wte_app (WF x w) (WB x w))
(wte_app WF’ WB’).
wtes-letdia :
wte-subst WTA ([x][wt] M x wt) M’ ->
% essentially same as lambda case + box case
({ww : world}
{wwt : {X : nat} {SS : wslist X}
welltyped_world SS ww}
{xx : exp} {wtt : {X : nat} {SS : wslist X}
welltyped_exp SS xx A’ ww}
277
{thm : {X : nat} {SS : wslist X}
{B : typ} {WO : world} {N : exp}
{WTN : welltyped_exp SS N B WO}
wte-subst WTN ([_][_] wtt X SS) (wtt X SS)}
wte-subst WTA ([x][w] N ww wwt xx wtt x w)
(N’ ww wwt xx wtt)) ->
wte-subst WTA ([x][wt] wte_letdia (M x wt)
([ww : world][wwt][xx : exp][wtt]
N ww wwt xx wtt x wt))
(wte_letdia M’ ([ww][wwt][xx][wtt] N’ ww wwt xx wtt)).
wtes-leta :
wte-subst WTA ([x][wt] M x wt) M’ ->
({xx : exp} {wtt : {X : nat} {SS : wslist X}
welltyped_exp SS xx A’ W’}
{thm : {X : nat} {SS : wslist X}
{B : typ} {WO : world} {N : exp}
{WTN : welltyped_exp SS N B WO}
wte-subst WTN ([_][_] wtt X SS) (wtt X SS)}
wte-subst WTA ([x][w] N xx wtt x w)
(N’ xx wtt)) ->
wte-subst WTA ([x][wt] wte_leta (M x wt)
([xx : exp][wtt]
N xx wtt x wt))
(wte_leta M’ ([xx][wtt] N’ xx wtt)).
%block bsubst :
some {A’ : typ} {W’ : world}
block {xx : exp} {ww : {X : nat} {SS : wslist X} welltyped_exp SS xx A’ W’}
{thm : {X : nat} {SS : wslist X}
{B : typ} {WO : world} {N : exp}
{WTN : welltyped_exp SS N B WO}
wte-subst WTN ([_][_] ww X SS) (ww X SS)}.
278
label-lemma :
{W : wdlist N} {S : wslist N} {J : nat} J < N ->
welltyped_exp S (lab L) A (wconst J) ->
welltyped_worlds S W ->
lookupv W J L V VP ->
welltyped_exp S V A (wconst J) -> type.
%mode label-lemma +W +S +J +LT +WE +WW +L -WV.
llfl-search :
ll-findlab S J TTS LIV WTDS WE ->
ll-findlab S J (tts_next TTS) (lookinv_next LIV)
(wtds_one WTDS _) WE.
llfl-found :
ll-findlab S J tts_found lookinv_found (wtds_one _ WE) WE.
llfw-search :
ll-findworld S J WDTL STL J’ LTIN WTWTL LUVTL PLUS’ WE ->
plus-flip PLUS PLUS’ ->
ll-findworld S J
(wd// _ _ WDTL) (ws// _ _ STL) (s J’)
(lti_next LTIN)
(wtws’_one WTWTL _ _)
(lookupv_next LUVTL) (PLUS : plus IDX (s J’) J) WE.
llfw-found :
ll-findworld S IDX=J
279
(wd// _ VL _) (ws// _ VTT _) 0
(lti_found LTIM TTS)
(wtws’_one _ _ WTDS)
(lookupv_found LUVM LIV) PLUS WE
<- plus-zero PLUS EQP
<- minus-eq LUVM LTIM EQO
<- lookinv-resp EQO LIV LIV’
<- ll-findlab S IDX=J TTS LIV’ WTDS WE.
label-lemma-unwrap :
% lt seems unnecessary -- well typedness will ensure
% that the label is in range.
label-lemma W S J LT
(wte_lab LTI : welltyped_exp S (lab L) A (wconst J))
(wtws WTW’) LV WE
<- plus-commutes p0 POO
<- ll-findworld S J W S J LTI WTW’ LV POO WE.
clabel-lemma :
{W : wdlist N}
{S : wslist N}
{J : nat}
J < N ->
clabtype_is S L A J ->
welltyped_worlds S W ->
lookupc W J L K ->
welltyped_cont S K A (wconst J) -> type.
%mode clabel-lemma +W +S +J +LT +WE +WW +L -WC.
cllfl-search :
cll-findlab S J TTS LIC WTCS WC ->
cll-findlab S J (tts_next TTS) (lookinc_next LIC)
(wtcs_one WTCS _) WC.
cllfl-found :
cll-findlab S J tts_found lookinc_found (wtcs_one WTCS WC) WC.
cllfw-search :
cll-findworld S J WDTL STL J’ LTIN WTWTL LUVTL PLUS’ WC ->
plus-flip PLUS PLUS’ ->
cll-findworld S J
(wd// _ _ WDTL) (ws// _ _ STL) (s J’)
(clti_next LTIN)
(wtws’_one WTWTL _ _)
(lookupc_next LUVTL) (PLUS : plus IDX (s J’) J) WC.
cllfw-found :
cll-findworld S IDX=J
280
(wd// CL _ _) (ws// CTT _ _) 0
(clti_found LTIM TTS)
(wtws’_one _ WTCS _)
(lookupc_found LUVM LIV) PLUS WE
<- plus-zero PLUS EQP
<- minus-eq LUVM LTIM EQO
<- lookinc-resp EQO LIV LIV’
<- cll-findlab S IDX=J TTS LIV’ WTCS WE.
clabel-lemma-unwrap :
clabel-lemma W S J LT (CLTI : clabtype_is S L A J)
(wtws WTW’) LV WC
<- plus-commutes p0 POO
<- cll-findworld S J W S J CLTI WTW’ LV POO WC.
addlabel-lemma :
{S : wslist N} {A : typ} {J : nat} J < N ->
welltyped_exp S V A (wconst J) ->
welltyped_worlds S W ->
add_value W J V VP W’ L ->
% out
{S’ : wslist N}
extends S S’ ->
labtype_is S’ L A J ->
welltyped_worlds S’ W’ -> type.
%mode addlabel-lemma +S +A +J +LT +WTE +WTWS +AV -S’ -EXT -LTI -WTWS’.
news-trace-zero :
news-trace 0 A (ws// CT BT S) (ws// CT (tt// A BT) S).
news-trace-succ :
news-trace J A S S’ ->
news-trace (s J) A (ws// CT BT S) (ws// CT BT S’).
all-newstore :
% looking through this storetype tail for this offset
{SL : wslist M} {JL : nat} JL < M ->
% and will add this type
{A : typ} add_value WL JL V VP WL’ L ->
welltyped_worlds’ M IDX S SL WL ->
% outputs
{SL’ : wslist M}
extends SL SL’ ->
labtype_is SL’ L A JL ->
news-trace JL A SL SL’ -> type.
%mode all-newstore +SL +JL +LT +A +AV +WT’ -SL’ -EXT -LTI -NT.
allns-miss :
all-newstore TTL J LT A AV WT’ TTL’ E LTI NT ->
all-newstore (ws// CT BT TTL) (s J) (lts LT) A
(addv_next AV) (wtws’_one WT’ _ _)
(ws// CT BT TTL’)
(ex-one E extendst-same extendst-same) (lti_next LTI)
(news-trace-succ NT).
281
allns-hit :
minus-self SIZE-1 M0 ->
ex-id TTL E ->
all-newstore (ws// CT BT TTL) 0 lt0 A (addv_found (BD : vlist SIZE-1))
% this just gets us |BT| = |BD|
(wtws’_one _ _ _)
(ws// CT (tt// A BT) TTL)
(ex-one E extendst-same extendst-cons)
(lti_found M0 tts_found) news-trace-zero.
% after extending a world with a label, the worlds are still well-typed.
all-still-wtws :
{S : wslist N} {S’ : wslist N} {SL : wslist M}
{SL’ : wslist M} {WL : wdlist M} {WL’ : wdlist M} {JL : nat}
welltyped_exp S’ V A (wconst J) ->
welltyped_worlds’ M IDX S SL WL ->
add_value WL JL V VP WL’ L ->
extends S S’ ->
% must know that we haven’t changed S except where we did addvalue.
news-trace JL A SL SL’ ->
plus JL IDX J ->
extends SL SL’ ->
welltyped_worlds’ M IDX S’ SL’ WL’ -> type.
%mode all-still-wtws +S +S’ +SL +SL’ +WL +WL’ +JL +WTE +WT +AV +E +NT +P +EE -WT’.
asw-hit :
weaken-wtws EXTS WTWS WTWS’ ->
weaken-wtcs EXTS WTCS WTCS’ ->
weaken-wtds EXTS WTDS WTDS’ ->
wte-resp _ _ EQ’ WTE WTE’ ->
eq-refl EQ EQ’ ->
plus-zero-eq PLUS EQ ->
all-still-wtws S S’ % nb need to ensure that SL = SL’
(ws// CT BT SL) (ws// CT (tt// A BT) SL)
(wd// CD BD WL) (wd// CD (v// V VP BD) WL) 0
WTE (wtws’_one WTWS WTCS WTDS)
(addv_found BD) EXTS news-trace-zero PLUS
(ex-one E extendst-same extendst-cons)
(wtws’_one WTWS’ WTCS’
(wtds_one WTDS’ WTE’)).
asw-skip :
weaken-wtds EXTS WTD WTD’ ->
weaken-wtcs EXTS WTC WTC’ ->
all-still-wtws S S’ SL SL’ WL WL’ JL WTE WTWS’ AV EXTS SF PLUS’ E WTW2 ->
plus-commutes PLUSR’ PLUS’ ->
plus-flip PLUSR PLUSR’ ->
plus-commutes PLUS PLUSR ->
all-still-wtws S S’ (ws// CT BT SL) (ws// CT BT SL’)
(wd// CD BD WL) (wd// CD BD WL’) (s JL)
WTE (wtws’_one WTWS’ WTC WTD)
(addv_next AV)
EXTS
(news-trace-succ SF) PLUS
(ex-one E _ _) (wtws’_one WTW2 WTC’ WTD’).
% strategy for addlabel-lemma: first, recurse to create S’ and EXT and LTI.
% these are the *inputs* to the code that gets WTWS.
all-unwrap :
all-still-wtws S S’ S S’ W W’ J WTE’ WTWS’ AV EXT NT p0 EXT WTWS2 ->
weaken-wte EXT WTE WTE’ ->
all-newstore S J LT A AV WTWS’ S’ EXT LTI NT ->
282
addlabel-lemma S A J LT WTE (wtws WTWS’) AV S’ EXT LTI (wtws WTWS2).
acl-newstore :
{SL : wslist M} {JL : nat} JL < M -> {A : typ}
add_cont WL JL K WL’ L -> welltyped_worlds’ M IDX S SL WL ->
{SL’ : wslist M} extends SL SL’ -> clabtype_is SL’ L A JL ->
cnews-trace JL A SL SL’ -> type.
%mode acl-newstore +SL +JL +LT +A +AV +WT’ -SL’ -EXT -LTI -NT.
aclns-miss :
acl-newstore TTL J LT A AC WT’ TTL’ E LTI NT ->
acl-newstore (ws// CT BT TTL) (s J) (lts LT) A
(addc_next AC) (wtws’_one WT’ _ _)
(ws// CT BT TTL’)
(ex-one E extendst-same extendst-same) (clti_next LTI)
(cnews-trace-succ NT).
aclns-hit :
minus-self SIZE-1 M0 ->
ex-id TTL E ->
acl-newstore (ws// CT BT TTL) 0 lt0 A (addc_found (CD : clist SIZE-1))
(wtws’_one _ _ _)
(ws// (tt// A CT) BT TTL)
(ex-one E extendst-cons extendst-same)
(clti_found M0 tts_found) cnews-trace-zero.
% after extending a world with a label, the worlds are still well-typed.
acl-still-wtws :
{S : wslist N} {S’ : wslist N} {SL : wslist M}
{SL’ : wslist M} {WL : wdlist M} {WL’ : wdlist M} {JL : nat}
welltyped_cont S’ K A (wconst J) ->
welltyped_worlds’ M IDX S SL WL ->
add_cont WL JL K WL’ L ->
extends S S’ ->
% must know that we haven’t changed S except where we did addvalue.
cnews-trace JL A SL SL’ ->
plus JL IDX J ->
extends SL SL’ ->
welltyped_worlds’ M IDX S’ SL’ WL’ -> type.
%mode acl-still-wtws +S +S’ +SL +SL’ +WL +WL’ +JL +WTE +WT +AV +E +NT +P +EE -WT’.
acsw-hit :
weaken-wtws EXTS WTWS WTWS’ ->
283
weaken-wtcs EXTS WTCS WTCS’ ->
weaken-wtds EXTS WTDS WTDS’ ->
wtc-resp _ _ EQ’ WTC WTC’ ->
eq-refl EQ EQ’ ->
plus-zero-eq PLUS EQ ->
acl-still-wtws S S’
% nb need to ensure that SL = SL’
(ws// CT BT SL) (ws// (tt// A CT) BT SL)
(wd// CD BD WL) (wd// (c// K CD) BD WL) 0
WTC (wtws’_one WTWS WTCS WTDS)
(addc_found CD) EXTS cnews-trace-zero PLUS
(ex-one E extendst-cons extendst-same)
(wtws’_one WTWS’ (wtcs_one WTCS’ WTC’) WTDS’).
acsw-skip :
weaken-wtds EXTS WTDS WTDS’ ->
weaken-wtcs EXTS WTCS WTCS’ ->
acl-still-wtws S S’ SL SL’ WL WL’ JL WTC WTWS’ AV EXTS SF PLUS’ E WTW2 ->
plus-commutes PLUSR’ PLUS’ ->
plus-flip PLUSR PLUSR’ ->
plus-commutes PLUS PLUSR ->
acl-still-wtws S S’ (ws// CT BT SL) (ws// CT BT SL’)
(wd// CD BD WL) (wd// CD BD WL’) (s JL)
WTC (wtws’_one WTWS’ WTCS WTDS)
(addc_next AV)
EXTS
(cnews-trace-succ SF) PLUS
(ex-one E _ _) (wtws’_one WTW2 WTCS’ WTDS’).
acl-unwrap :
acl-still-wtws S S’ S S’ W W’ J WTC’ WTWS’ AC EXT NT p0 EXT WTWS2 ->
weaken-wtc EXT WTC WTC’ ->
acl-newstore S J LT A AC WTWS’ S’ EXT CLTI NT ->
addcont-lemma S A J LT WTC (wtws WTWS’) AC S’ EXT CLTI (wtws WTWS2).
lprog-sub-next :
lprog-sub VT’ VD’ TTS’ LIV’ ->
lprog-sub (tt// _ VT’) (v// _ _ VD’)
(tts_next TTS’) (lookinv_next LIV’).
label-progress-next :
label-progress LTI WTWS LUV ->
label-progress (lti_next LTI) (wtws’_one WTWS _ _)
(lookupv_next LUV).
label-progress-found :
lprog-sub VT VD TTS LIV ->
284
label-progress (lti_found (MIN : minus _ _ OFF) TTS)
(wtws’_one _ _
(DTS : welltyped_datas S VT VD _))
(lookupv_found MIN LIV).
clprog-sub-next :
clprog-sub CT’ CD’ TTS’ LIC’ ->
clprog-sub (tt// _ CT’) (c// _ CD’)
(tts_next TTS’) (lookinc_next LIC’).
clabel-progress :
clabtype_is SL L A J ->
welltyped_worlds’ REST IDX S SL WL ->
lookupc WL J L K -> type.
%mode clabel-progress +CLTI +WT -LUC.
clabel-progress-next :
clabel-progress CLTI WTWS LUC ->
clabel-progress (clti_next CLTI) (wtws’_one WTWS _ _)
(lookupc_next LUC).
clabel-progress-found :
clprog-sub CT CD TTS LIC ->
clabel-progress (clti_found (MIN : minus _ _ OFF) TTS)
(wtws’_one _
(CTS : welltyped_conts S CT CD _) _)
(lookupc_found MIN LIC).
% ditto, continuations
addcont-progress :
{W : wdlist N}
{J : nat}
J < N ->
{K : cont}
add_cont W J K W’ L -> type.
285
%mode addcont-progress +W +J +LT +K -AV.
% or value table...
rval-inbounds : {S : wslist X} labtype_is S L A J -> J < X -> type.
%mode rval-inbounds +S +C -LT.
no-cf-bottom :
{V : exp} value V ->
welltyped_exp S V _|_ W ->
({N : nat} {NN : network N} {NN’ : network N} NN |-> NN’) -> type.
%mode no-cf-bottom +A +B +C -D.
ncb-abort :
notval-abort V FALSE ->
no-cf-bottom (abort _) V (wte_abort _) FALSE.
%%%%
%%%% Theorems.
%%%%
%%%%% Preservation
progress : {NN : network N} wellformed S NN -> (NN |-> NN’) -> type.
%mode progress +NN +WF -STEP.
% cases on evaluating
proge-fst : progress (net W C K (eval (fst M))) _ step_pfst.
proge-snd : progress (net W C K (eval (snd M))) _ step_psnd.
proge-here : progress (net W C K (eval (here M))) _ step_phere.
proge-hold : progress (net W C K (eval (hold M))) _ step_phold.
286
proge-pair1 : progress (net W C K (eval (pair M N))) _ step_ppair.
proge-addr : progress (net W C K (eval (addr _ _))) _ step_taddr.
proge-lam : progress (net _ _ _ (eval (lam B))) _ step_tlam.
proge-box : progress (net _ _ _ (eval (box B))) _ step_tbox.
proge-app : progress (net _ _ _ (eval (app M N))) _ step_papp.
proge-unbox : progress (net _ _ _ (eval (unbox M))) _ step_punbox.
proge-letdia : progress (net _ _ _ (eval (letdia M N))) _ step_pletdia.
proge-leta : progress (net _ _ _ (eval (leta M N))) _ step_pleta.
proge-abort : progress (net W J _ (eval (abort M))) _ step_abort.
proge-lab :
label-progress LTI WTWS LUV
-> progress (net _ _ _ (eval (lab _)))
(wf_net _ _ (wt_eval (wte_lab LTI)) _ (wtws WTWS))
(step_lab LUV).
proge-held :
progress (net _ _ _ (eval (held V)))
(wf_net _ _ (wt_eval (wte_held _ VAL)) _ _)
(step_theld :
_ |-> net _ _ _ (ret _ (valheld VAL))).
% cases on returning
% loop to ourselves
progr-done : progress (net W C finish (ret V _)) WF step_done.
% impossible case
% is there a better way to do this? Unlike the other cases,
% Twelf can’t seem to figure out on its own that there is no
% canonical form for bottom
progr-fabort :
no-cf-bottom M V WT RIDICULOUS ->
progress (net W J fabort (ret M V))
(wf_net _ _|_ (wt_ret WT) wtc_fabort _)
(RIDICULOUS _ _ (net W J fabort (ret M V))).
287
(wf_net _ _ _ _ _)
step_fapp.
proge-throw :
clabel-progress CLTI WTWS LUC ->
progress (net W J _ (eval (throw M (caddr (wconst JNEW) L))))
(wf_net LT _ (wt_eval (wte_throw (wtce_caddr CLTI) WE))
_ (wtws WTWS))
(step_throw LUC).
%%%%% Preservation
288
presv-rsnd : ex-id S E ->
preservation _ S (wf_net LT (_ & B) (wt_ret (wte_pair _ WEB)) (wtc_snd WC) WTWS)
step_rsnd S E
(wf_net LT B (wt_ret WEB) WC WTWS).
% this case is a bit more complex because we have our own substitution
% theorem; see wwte-lam
presv-rapp : ex-id S E ->
wte-subst WA WF WS ->
preservation _ S (wf_net LT A (wt_ret WA) (wtc_app2 (wte_lam WF) WC) WTWS)
step_rapp S E
(wf_net LT B (wt_eval WS) WC WTWS).
% same here, but even worse because we have to substitute for the expression
% and the world
presv-rletdia : ex-id S E ->
wte-subst (wte_lab LTI) WN’ WE’ ->
% bm
({m:exp} {wt : {X : nat} {SS : wslist X} welltyped_exp SS m A (wconst JOTHER)}
{thm : {Y : nat} {s : wslist Y} {s’ : wslist Y}
{ex : extends s s’}
weaken-wte ex (wt Y s) (wt Y s’)}
wte-wsubst JOTHER LTOTHER ([wwt] WN (wconst JOTHER) wwt m wt) (WN’ m wt)) ->
rval-inbounds S LTI LTOTHER ->
preservation (net W J (fletdia K N) (ret (addr (wconst JOTHER) L) valaddr))
S (wf_net LT (? A) (wt_ret (wte_addr LTI))
(wtc_letdia WC WN) WTWS)
step_rletdia S E
(wf_net LT C (wt_eval WE’) WC WTWS).
289
presv-runbox : ex-id S E ->
wte-wsubst J LT (WB (wconst J)) WB’ ->
preservation _ S (wf_net LT (! A) (wt_ret (wte_box WB))
(wtc_unbox WC) WTWS)
step_runbox S E
(wf_net LT A (wt_eval WB’) WC WTWS).
presv-rhere :
% insertion preserves and creates all sorts of stuff
weaken-wtc EXT WC WC’ ->
addlabel-lemma S A _ LT WTE WTWS AV S’ EXT LTI WTWS’ ->
preservation _ S (wf_net LT A (wt_ret WTE) (wtc_here WC) WTWS)
(step_rhere AV) S’ EXT
(wf_net LT (? A) (wt_ret (wte_addr LTI)) WC’ WTWS’).
presv-lab :
% lookup preserves type
label-lemma W S J LT WE WTWS LUV WEL ->
ex-id S E ->
290
preservation _ S
(wf_net LT A (wt_eval WE) WTC WTWS)
(step_lab LUV) S E
(wf_net LT A (wt_ret WEL) WTC WTWS).
presv-rreturnbox :
vshift MOB WM VALUE _ WM’ ->
clabel-lemma W S J LTNEW CLTI WTWS LUC WTC ->
rcont-inbounds S CLTI LTNEW ->
ex-id S E ->
preservation (net _ _ _ (ret _ VALUE)) S
(wf_net LT A (wt_ret WM) (wtc_freturn CLTI MOB) WTWS)
(step_rreturn LUC) S E
(wf_net LTNEW A (wt_ret WM’) WTC WTWS).
presv-throw :
clabel-lemma W S JNEW LTNEW CLTI (wtws WTWS) LUC WTC ->
rcont-inbounds S CLTI LTNEW ->
ex-id S E ->
preservation
(net W JOLD _ (eval (throw M (caddr (wconst JNEW) L)))) S
(wf_net LT _ (wt_eval (wte_throw (wtce_caddr CLTI) WE)) _
(wtws WTWS))
(step_throw LUC) S E
(wf_net LTNEW _ (wt_eval WE) WTC (wtws WTWS)).
291
weaken-wte EXT (WTBODY c wt) (WTBOD’ c wt)) ->
weaken-wtc EXT WC WC’ ->
addcont-lemma S A _ LT WC WTWS AC S’ EXT CLTI WTWS’ ->
preservation _ S (wf_net LT A (wt_eval (wte_letcc WTBODY)) WC WTWS)
(step_rletcc AC) S’ EXT
(wf_net LT A (wt_eval WTS) WC’ WTWS’).
presv-get :
weaken-wte EXT WT WT’ ->
addcont-lemma S A _ LT WC WTWS AC S’ EXT CLTI WTWS’ ->
preservation _ S
(wf_net LT A
(wt_eval (wte_get (wtw_const _ LTNEW) WT MOB)) WC WTWS)
(step_get AC) S’ EXT
(wf_net LTNEW A (wt_eval WT’)
(wtc_freturn CLTI MOB) WTWS’).
%%%%
%%%% Check lemmas and theorems.
%%%%
292
%total D1 (val-csubst D1 D2 D3).
%total D2 (wte-csubst D1 D2 D3).
A.6 Validity
%% Soundness and completeness of VS5
% Propositions
% nontrivial, nonmobile
=> : prop -> prop -> prop. %infix right 8 =>.
% so that worlds can appear in props
at : prop -> world -> prop. %infix none 6 at.
sh : (world -> prop) -> prop.
top : prop.
bot : prop.
% Natural deduction
% Structural
293
get : mobile A -> A @ W -> A @ W’.
put : mobile A -> A @ W -> (A @ W’ -> C @ W’’) -> C @ W’’.
% Universal reasoning
uvar : ˜ Af -> (Af W) @ W.
shI : ({w:world} (Af w) @ w) -> (sh Af @ W).
shE : sh Af @ W -> (˜ Af -> C @ W) -> C @ W.
% Implication
=>I : (A @ W -> B @ W) -> (A => B @ W).
=>E : (A => B @ W) -> A @ W -> B @ W.
% Hybrid
atI : A @ W -> A at W @ W.
atE : A at W’ @ W -> (A @ W’ -> C @ W) -> C @ W.
% Top, bottom
topI : top @ W.
botE : bot @ W -> C @ W.
% Sequent calculus
size-top : size.
size-bot : size.
size-=> : size -> size -> size.
% in particular, ignore the world here
size-at : size -> size.
size-sh : size -> size.
294
% lsize- : size -> size.
cut : {A:prop}
{S:size}
has-size A S ->
conc A W ->
(hyp A W -> conc C U) ->
conc C U ->
type.
%mode cut +A +S +H +D +E -F.
295
<- cut (Af W1) _ (Hs W1) (D W1) F F’.
% Expansion
exp : mobile A -> hyp A W -> conc A W’ -> type. %name exp AM.
%mode +{A:prop} +{M:mobile A}
+{W:world} +{W’:world}
+{H:hyp A W} -{C:conc A W’}
exp M H C.
- : shift M (copy ([h : hyp (Cf W2) W2] D h) (VH : vhyp Cf)
: conc A W) W’ (copy D’ VH)
<- ({ha} shift M (D ha) W’ (D’ ha)).
% Initial cuts
ci_l : cut A _ _ (init H) ([h] E h) (E H).
ci_r : cut A _ _ D ([h] init h) D.
% Principal cuts
c_=> : cut (A1 => A2) _ (has-=> Hb Ha) (=>R ([h1] D2 h1))
([h] =>L (E1 h) ([h2] E2 h h2) h) F
<- cut (A1 => A2) _ (has-=> Hb Ha) (=>R ([h1] D2 h1)) ([h] E1 h) E1’
<- ({h2:hyp A2 W}
cut (A1 => A2) _ (has-=> Hb Ha)
(=>R ([h1] D2 h1))
296
([h] E2 h h2) (E2’ h2))
<- cut A1 _ Ha E1’ ([h1] D2 h1) F1
<- cut A2 _ Hb F1 ([h2] E2’ h2) F.
297
%mode uhyp +U -VH.
% uvars
ns_u : ndseq (uvar U) (C _)
<- uhyp U C.
% shamrock
ns_shI : ndseq (shI Vf) (shR D)
<- ({w} ndseq (Vf w) (D w)).
% Implication
ns_=>I : ndseq (=>I ([u1] N2 u1)) (=>R ([h1] D2 h1))
<- ({u1:A1 @ W} {h1:hyp A1 W}
ndseq u1 (init h1) -> ndseq (N2 u1) (D2 h1)).
% Hybrid
ns_atI : ndseq (atI N) (atR D)
<- ndseq N D.
ns_atE : ndseq (atE N1 ([u : A @ W] N2 u)) D
<- ndseq N1 D1’
<- ({u}{h} ndseq u (init h) -> ndseq (N2 u) (D2 h))
<- can-has-size (A at W) Hs
<- cut (A at W) _ Hs D1’ ([h] atL ([h’] D2 h’) h) D.
% Structural
ns_get : ndseq (get MOB N : A @ W) (F : conc A W)
<- ndseq N (D : conc A W’)
<- shift MOB D W F.
ns_put : ndseq (put MOB M ([u:A @ W’] (N u) : C @ W’’)) (F : conc C W’’)
<- ndseq M (D : conc A W)
<- ({u}{h} ndseq u (init h) -> ndseq (N u) (E h))
<- shift MOB D W’ D’
<- can-has-size A Hs
<- cut A _ Hs D’ E F.
298
seqnd : conc A W -> A @ W -> type.
hypnd : hyp A W -> A @ W -> type.
vhypnd : vhyp Af -> ˜ Af -> type.
%mode seqnd +D -N.
%mode hypnd +H -N.
%mode vhypnd +V -U.
% Init
sn_init : seqnd (init H) N
<- hypnd H N.
% Hybrid
sn_atR : seqnd (atR D : conc (A at W’) W)
(get atmob (atI N) : (A at W’) @ W)
<- seqnd (D : conc A W’) (N : A @ W’).
sn_atL : seqnd (atL ([h] D h) H) (atE (get atmob N1) ([u] N2 u))
<- ({h : hyp A W}{u : A @ W}
hypnd h u -> seqnd (D h) (N2 u))
<- hypnd H N1.
% Implication
sn_=>R : seqnd (=>R ([h] D h)) (=>I ([u : A @ W] N u))
<- ({h:hyp A W} {u:A @ W}
hypnd h u ->
seqnd (D h) (N u)).
% Shamrock
sn_shR : seqnd (shR D) (shI N)
<- ({w} seqnd (D w) (N w)).
299
A.7 Operational semantics and type safety for validity
%% Operational semantics and type safety for VS5
%% Thanks: Rob Simmons, Jason Reed, Dan Licata
% Propositions
% Natural deduction
% Structural
get : mobile A -> A @ W -> A @ W’.
put : mobile A -> A @ W -> (˜ ([w] A) -> C @ W) -> C @ W.
val : A / W -> A @ W.
% Universal reasoning
uvar : ˜ Af -> (Af W) / W.
shI : ({w:world} (Af w) / w) -> (sh Af / W).
shE : sh Af @ W -> (˜ Af -> C @ W) -> C @ W.
% Implication
=>I : (A / W -> B @ W) -> (A => B / W).
=>E : (A => B @ W) -> A @ W -> B @ W.
% Hybrid
atI : A @ W -> A at W @ W.
atIv : A / W’ -> A at W’ / W.
atE : A at W’ @ W -> (A / W’ -> C @ W) -> C @ W.
% True
topI : top / W.
300
<- ({y} vsubst Vf ([x] E x y) (E’ y)).
vv-sh : vsubstv Vf ([x] shI ([w] Vf2 x w)) (shI Vf2’)
<- ({w} vsubstv Vf ([x] Vf2 x w) (Vf2’ w)).
% We cannot prove
% extract-lam’ : (A => B) / W -> (A / W -> B @ W) -> type
% directly. If Twelf splits on the first argument, then
% it sees that =>I and uvar can apply, but it is unable
% to express the extra unification constraint that
% (A => B) = Af W.
% Since it cannot resolve the constraint, coverage checking
% fails. This is unfortunate because, in a world (context) where
% there are no ˜ hypotheses, like the one we use, the
% constraint does not matter---we would be able to eliminate
% the uvar case if we simply forged ahead ignoring the constraint.
% The solution is to internalize the constraint using the
% judgment eq. If we prevent Twelf from splitting on this input,
% then it can eliminate the uvar case because of the regular world,
% and then it can eliminate other constructors like shI and atIv
% by subsequently splitting on the eq input.
301
%mode extract-sh +M +E -L.
- : extract-sh (shI Vf) _ Vf.
302
% Forward declarations of type families and blocks.
% It also becomes necessary to declare modes for some things, even though
% they are not used as logic programs. World is a zero-place relation!
%mode world.
% internal language
ty : type. %name ty A a.
exp : type.
val : type.
vval : type.
ofv : val -> ty -> world -> type.
ofvv : vval -> (world -> ty) -> type.
% CPS language
ctyp : type. %name ctyp A a.
cval : type. %name cval V v.
cvval : type. %name cvval VV vv.
ttoct : ty -> ctyp -> type. %name ttoct TTOCT ttoct.
ttoctf : (world -> ty) -> (world -> ctyp) -> type. %name ttoctf TTOCTF ttoctf.
cofv : cval -> ctyp -> world -> type. %name cofv WV wv.
cofvv : cvval -> (world -> ctyp) -> type. %name cofvv WVV wvv.
% CPS conversion
tocpsv- : {WV : ofv V A W} {CT : ttoct A CA} {WCV : cofv CV CA W} type.
tocpsvv- : {WV : ofvv V Af} {CT : ttoctf Af CAf} {WCV : cofvv CV CAf} type.
%block blockcvvar :
some {Af : world -> ty} {Af’ : world -> ctyp} {CTA : ttoctf Af Af’}
block {x}{xof : ofvv x Af}
{x’}{x’of : cofvv x’ Af’}
{thm:tocpsvv- xof CTA x’of}.
303
% The external language of ML5 is just like the internal language,
% but it has some derived connectives like Box and Dia.
tye : type.
expe : type.
vale : type.
vvale : type.
% %%%%%%%%%%%%%%%%%%%%%
% EL typing rules
% %%%%%%%%%%%%%%%%%%%%%
% |- e : t @ w
ofe : expe -> tye -> world -> type.
ofve : vale -> tye -> world -> type.
% x ˜ w.t as hypothesis
ofvve : vvale -> (world -> tye) -> type.
% Mobility
mobilee : tye -> type.
%mode mobilee *A.
304
addrMe : mobilee (addre W).
atMe : mobilee (A ate W).
&Me : mobilee A -> mobilee B ->
mobilee (A &e B).
boxMe : mobilee (!e A).
diaMe : mobilee (?e A).
shaMe : mobilee (she A).
305
ofe (heree M) (?e A) W.
?Ee : ofe M (?e A) W ->
({w:world}
{a:vale}{wa : ofve a (addre w) W}
{x:vale}{wx : ofve x A w} ofe (N w a x) C W) ->
ofe (letde M N) C W.
306
addrM : mobile (addr W).
atM : mobile (A at W).
&M : mobile A -> mobile B ->
mobile (A & B).
allM : ({w} mobile (A w)) -> mobile (all [w] A w).
existsM : ({w} mobile (A w)) -> mobile (exists [w] A w).
shaM : mobile (sh Af).
% |- e : t @ w
of : exp -> ty -> world -> type.
% (others were forward-declared)
vvI : ({w} ofv (Vf w) (Af w) w) -> ofvv (vv Vf) Af.
% containments
ofvalue : of (value V) A W <- ofv V A W.
ofvvalid : ofv (valid V) (Af W) W <- ofvv V Af.
existsI : {A:world -> ty} % often need this annotation for Twelf’s sake
ofv V (A W’) W ->
ofv (pack W’ V) (exists [w] A w) W.
existsE : of M (exists A) W ->
({w:world}{v:val} ofv v (A w) W ->
of (N w v) C W) ->
of (unpack M N) C W.
oflet : of M A W ->
({y:val}{ofy : ofv y A W}
of (N y) C W) ->
307
of (let M N) C W.
ec : type. %name ec E.
efinish : ec.
% waiting for world addr
eget : ec -> exp -> ec.
% waiting for val
eput : ec -> (vval -> exp) -> ec.
% evaluating remotely, returning to the world
eget2 : ec -> world -> ec.
eapp1 : ec -> exp -> ec.
eapp2 : ec -> val -> ec.
ewapp : ec -> world -> ec.
efst : ec -> ec.
esnd : ec -> ec.
eleta : ec -> (val -> exp) -> ec.
eunpack : ec -> (world -> val -> exp) -> ec.
% waiting for exp to be evaluated
elet : ec -> (val -> exp) -> ec.
% E is a continuation expecting A at W
ofec : ec -> ty -> world -> type. %name ofec WE.
308
ofec_eget2 : mobile A ->
ofec E A W ->
ofec (eget2 E W) A W’.
% in empty context
wellformed : ec -> exp -> world -> ty -> type.
step : world -> ec -> exp -> world -> ec -> exp -> type. %name step S.
309
step W (eput E N) (value V) W E (N V’).
% lemmas.
310
momopair : momo (mopair MV1 MV2) (&Iv OM1 OM2)
(vvI [w] &Iv (ofvvalid D1) (ofvvalid D2))
<- momo MV1 OM1 D1
<- momo MV2 OM2 D2.
momoall : momo (moall MV) (allI OM) (vvI [w’] allI ([w] ofvvalid (D w)))
<- ({w:world}
momo (MV w) (OM w) (D w)).
momoexists :
momo (moexists MOV) (existsI Aw WF) (vvI [w’] (existsI _ (ofvvalid F)))
<- momo MOV WF F.
% safety theorem
prog : wellformed E M W A ->
step W E M W’ E’ M’ ->
type.
%mode prog +WF -S.
%%%%%%%%%%%%%%%%%%%%
% cases for progress
%%%%%%%%%%%%%%%%%%%%
% simple, pushing
prog_getp : prog (wf_mach WE (ofget MOB WR WA)) step_getp.
prog_allEp : prog (wf_mach WE (allE WA)) step_wappp.
prog_=>Ep : prog (wf_mach WE (=>E WF WA)) step_appp.
prog_&E1p : prog (wf_mach WE (&E1 WA)) step_fstp.
prog_&E2p : prog (wf_mach WE (&E2 WA)) step_sndp.
prog_exEp : prog (wf_mach WE (existsE _ _)) step_existsp.
prog_putp : prog (wf_mach WE (ofput MOB WM WN)) step_putp.
311
prog_letsfv : prog (wf_mach (ofec_elets _ _) (ofvalue (shI VALID))) step_letsr.
%%%%%%%%%%%%%%%%%%%%%%%%
% cases for preservation
%%%%%%%%%%%%%%%%%%%%%%%%
presv_putp : presv (wf_mach (WE : ofec E C W) (ofput MOB (WM : of M A W) WN)) step_putp
(wf_mach (ofec_eput WE WN MOB) WM).
presv_putr : momo MOV WV VALID ->
presv (wf_mach (ofec_eput WE WN MOB) (ofvalue WV)) (step_putr MOV)
(wf_mach WE (WN _ VALID)).
presv_fstp : presv (wf_mach WE (&E1 WP)) step_fstp
(wf_mach (ofec_efst WE) WP).
presv_sndp : presv (wf_mach WE (&E2 WP)) step_sndp
(wf_mach (ofec_esnd WE) WP).
presv_appp : presv (wf_mach WE (=>E WF WA)) step_appp
(wf_mach (ofec_eapp1 WA WE) WF).
presv_app1 : presv (wf_mach (ofec_eapp1 WA WE) (ofvalue WF)) step_app1
(wf_mach (ofec_eapp2 WF WE) WA).
presv_app2 : presv (wf_mach (ofec_eapp2 (=>I WF) WE) (ofvalue WA)) step_app2
(wf_mach WE (WF X WA)).
presv_existsp : presv (wf_mach WE (existsE WM WN)) step_existsp
(wf_mach (ofec_eunpack WN WE) WM).
312
step_letap (wf_mach (ofec_eleta WE WN) WM).
presv_letar : presv (wf_mach (ofec_eleta WE WN) (ofvalue (atIv WV)))
step_letar (wf_mach WE (WN _ WV)).
presv_vvinst : presv (wf_mach WE (ofvalue (ofvvalid (vvI WVV)))) step_vvinst
(wf_mach WE (ofvalue (WVV _))).
% continuation expressions
cfst : cval -> (cval -> cexp) -> cexp.
csnd : cval -> (cval -> cexp) -> cexp.
clocalhost : (cval -> cexp) -> cexp.
313
% continuation values
cpair : cval -> cval -> cval.
cheld : world -> cval -> cval.
clam : (cval -> cexp) -> cval.
cconst : world -> cval.
cwlam : (world -> cval) -> cval.
cpack : world -> cval -> cval.
c1 : cval.
% inclusion of cvvals in cvals
cvalid : cvval -> cval.
% internalization into sh modality
ch : cvval -> cval.
% the only vval
cvv : (world -> cval) -> cvval.
% ################
% Static Semantics
% ################
314
({v} cofv v (A W’) W ->
cof (N v) W) ->
cof (cwapp V W’ N) W.
co_unpack :
cofv V (cexists A) W ->
({w}{v} cofv v (A w) W ->
cof (N w v) W) ->
cof (cunpack V N) W.
315
ceqtyp_cont : ceqtyp A A’ -> ceqtyp (A ccont) (A’ ccont) -> type.
- : ceqtyp_cont ceqtyp_ ceqtyp_.
%mode ceqtyp_cont +A -C.
ceqtyp_sh : ({w} ceqtyp (A w) (A’ w)) -> ceqtyp (csh A) (csh A’) -> type.
- : ceqtyp_sh ([w] ceqtyp_) ceqtyp_.
%mode ceqtyp_sh +A -C.
ceqtyp_at : {W:world} ceqtyp A A’ -> ceqtyp (A cat W) (A’ cat W) -> type.
- : ceqtyp_at _ ceqtyp_ ceqtyp_.
%mode ceqtyp_at +W +A -B.
cget_pair : world -> cval -> cval -> cval -> type.
%mode cget_pair *W *A *B *C.
cgpair-it : cget_pair _ (cpair V1 V2) V1 V2.
cgpair-vv : cget_pair W (cvalid (cvv VV)) V1 V2
<- cget_pair W (VV W) V1 V2.
316
cgheld-it : cget_held _ (cheld W’ V) V.
cgheld-vv : cget_held W (cvalid (cvv VV)) V
<- cget_held W (VV W) V.
cget_pack : world -> cval -> world -> cval -> type.
%mode cget_pack *W *A *B *C.
cgpack-it : cget_pack _ (cpack W V) W V.
cgpack-vv : cget_pack W (cvalid (cvv VV)) W’ V’
<- cget_pack W (VV W) W’ V’.
cget_wlam : world -> cval -> (world -> cval) -> type.
%mode cget_wlam *W *A *B.
cgwlam-it : cget_wlam _ (cwlam V) V.
cgwlam-vv : cget_wlam W (cvalid (cvv VV)) V
<- cget_wlam W (VV W) V.
cget_lam : world -> cval -> (cval -> cexp) -> type.
%mode cget_lam *W *A *B.
cglam-it : cget_lam _ (clam C) C.
cglam-vv : cget_lam W (cvalid (cvv VV)) C
<- cget_lam W (VV W) C.
cf_const : cofv V A W -> ceqtyp A (caddr W’) -> cget_const W V W’ -> type.
%mode cf_const +O +E -W.
- : cf_const cov_const _ cgconst-it.
- : cf_const ((cov_valid (covv D)) : cofv _ _ W) EQ (cgconst-vv W’)
<- cf_const (D W) EQ W’.
cf_wlam : cofv V A W -> ceqtyp A (call A’) -> cget_wlam W V V’ -> type.
%mode cf_wlam +O +E -L.
- : cf_wlam (cov_wlam _) _ cgwlam-it.
- : cf_wlam ((cov_valid (covv D)) : cofv _ _ W) EQ (cgwlam-vv L)
<- cf_wlam (D W) EQ L.
cf_lam : cofv V A W -> ceqtyp A (A’ ccont) -> cget_lam W V C -> type.
317
%mode cf_lam +O +E -L.
- : cf_lam (cov_lam _) _ cglam-it.
- : cf_lam ((cov_valid (covv D)) : cofv _ _ W) EQ (cglam-vv L)
<- cf_lam (D W) EQ L.
cf_held : cofv V A W -> ceqtyp A (A’ cat W’) -> cget_held W V V’ -> type.
%mode cf_held +O +E -H.
- : cf_held (cov_held _) _ cgheld-it.
- : cf_held ((cov_valid (covv D)) : cofv _ _ W) EQ (cgheld-vv H)
<- cf_held (D W) EQ H.
cf_pair : cofv V A W -> ceqtyp A (A1 c& A2) -> cget_pair W V V1 V2 -> type.
%mode cf_pair +O +E -P.
- : cf_pair (cov_pair _ _) _ cgpair-it.
- : cf_pair ((cov_valid (covv D)) : cofv _ _ W) EQ (cgpair-vv P)
<- cf_pair (D W) EQ P.
cf_ch : cofv V A W -> ceqtyp A (csh Af) -> cget_ch W V VV -> type.
%mode cf_ch +O +E -P.
- : cf_ch (cov_ch _) _ cgch-it.
- : cf_ch ((cov_valid (covv D)) : cofv _ _ W) EQ (cgch-vv VV)
<- cf_ch (D W) EQ VV.
ci_pair : cofv V A W -> ceqtyp A (A1 c& A2) -> cget_pair W V V1 V2 ->
cofv V1 A1 W -> cofv V2 A2 W -> type.
%mode ci_pair +O +E +P -V1 -V2.
- : ci_pair (cov_pair D1 D2) _ cgpair-it D1 D2.
- : ci_pair ((cov_valid (covv D)) : cofv _ _ W) EQ (cgpair-vv P) D1 D2
<- ci_pair (D W) EQ P D1 D2.
318
({w} cofv (V’ w) (A’ w) W) -> type.
%mode ci_wlam +O +E +L -D.
- : ci_wlam (cov_wlam D) _ cgwlam-it D.
- : ci_wlam ((cov_valid (covv D)) : cofv _ _ W) EQ (cgwlam-vv L) D’
<- ci_wlam (D W) EQ L D’.
ci_pack : {A’: world -> ctyp} cofv V A W -> ceqtyp A (cexists A’) ->
cget_pack W V W’ V’ -> cofv V’ (A’ W’) W -> type.
%mode ci_pack +O +A’ +E +P -B.
- : ci_pack A’ (cov_pack A’ B : cofv (cpack W’ V’) (cexists A’) W) EQ
(cgpack-it : cget_pack W (cpack W’ V’) W’ V’) B.
- : ci_pack A’ ((cov_valid (covv D)) : cofv _ _ W) EQ (cgpack-vv Z) B
<- ci_pack A’ (D W) EQ Z B.
ci_held : cofv V A W -> ceqtyp A (A’ cat W’) -> cget_held W V V’ ->
cofv V’ A’ W’ -> type.
%mode ci_held +O +E +H -V.
- : ci_held (cov_held D) _ cgheld-it D.
- : ci_held ((cov_valid (covv D)) : cofv _ _ W) EQ (cgheld-vv H) DD
<- ci_held (D W) EQ H DD.
% progress
cprog : cof C W -> cstep W C W’ C’ -> type.
%mode cprog +COF -CSTEP.
% preservation
cpresv : cof C W -> cstep W C W’ C’ -> cof C’ W’ -> type.
%mode cpresv +COF +CSTEP -COF’.
319
- : cpresv (co_unpack WV WC) (cs_unpack (G : cget_pack _ _ W’ V)) (WC W’ V WV’)
<- ci_pack _ WV ceqtyp_ G WV’.
- : cpresv (co_leta WA WC) (cs_leta G) (WC _ WV) <- ci_held WA ceqtyp_ G WV.
- : cpresv (co_put MOB WV WC) (cs_put CM) (WC _ WVV) <- cmomo CM WV WVV.
- : cpresv (co_lets WV WC) (cs_lets G) (WC _ WVV) <- ci_ch WV ceqtyp_ G WVV.
- : cpresv (co_localhost WC) cs_localhost (WC _ cov_const).
320
ettoit_sh : ({w} ettoit (Af w) (Af’ w)) ->
ettoit (she Af) (sh Af’).
eqtyp_=> : eqtyp A A’ -> eqtyp B B’ -> eqtyp (A => B) (A’ => B’) -> type.
%mode eqtyp_=> +A +B -C.
- : eqtyp_=> eqtyp_ eqtyp_ eqtyp_.
eqtyp_sh : ({w} eqtyp (A w) (A’ w)) -> eqtyp (sh A) (sh A’) -> type.
%mode eqtyp_sh +A -B.
- : eqtyp_sh ([_] eqtyp_) eqtyp_.
ettoit_fun : ettoit A A’ -> ettoit A A’’ -> eqtyp A’ A’’ -> type.
%mode ettoit_fun +X +Y -Z.
321
- : ettoit_fun (ettoit_& E1 E2) (ettoit_& E1’ E2’) D3
<- ettoit_fun E1 E1’ D1
<- ettoit_fun E2 E2’ D2
<- eqtyp_& D1 D2 D3.
322
mobemob : mobilee A -> ettoit A A’ -> mobile A’ -> type.
%mode mobemob +BM +T -MOB.
323
- : elab- _ _ (=>Ee Df Da) ET _ (=>E Df’ Da’)
<- elab- _ _ Df (ettoit_=> ET’ ET) _ Df’
<- elab+ _ _ Da ET’ _ Da’.
% interesting cases
- : elab- _ _ (!Ee D) ET _
(atE (allE D’) [x][ofx] =>E (ofvalue ofx) (ofvalue unitI))
<- elab- _ _ D (ettoit_! ET) _ D’.
324
% we don’t need ’hold’ in the IL because we can just
% sequence the evaluation and use held.
- : elab- _ _ (atIe D) (ettoit_at ET) (let M’ ([y] value (held y)))
(oflet D’ ([y:val][wy:ofv y A’ W] ofvalue (atIv wy)))
<- elab- _ _ D ET _ (D’ : of M’ A’ W).
% other judgments
- : elab- _ _ (ofvaluee D) ET _ (ofvalue D’) <- elabv- _ _ D ET _ D’.
% interesting cases
- : elabv- (?e A) (theree Va V) (?Ive (WVa : ofve Va (addre W’) W) WV)
(ettoit_? ET)
_ (existsI _ (&Iv (atIv WV’) (WVa’)))
<- ettoit_gimme _ _ ET
<- elabv+ (addre W’) Va WVa ettoit_addr _ WVa’
<- elabv+ _ _ WV ET _ WV’.
%block blockve :
some {A:tye}{A’:ty}{W:world}{ETv : ettoit A A’}
block {ve : vale}{ove : ofve ve A W}{v : val}{ov : ofv v A’ W}
{thm : elabv- A ve ove ETv v ov}.
%block blockvve :
some {Af:world -> tye}{Af’:world -> ty}{ETf : ettoitf Af Af’}
325
block {ve : vvale}{ove : ofvve ve Af}{v : vval}{ov : ofvv v Af’}
{thm : elabvv Af ve ove ETf v ov}.
ttoctf/ : ttoctf Af Af’ <- ({w : world} ttoct (Af w) (Af’ w)).
% sanity check
%worlds (blockw) (ttoct _ _) (ttoctf _ _).
%total (D E) (ttoct D _) (ttoctf E _).
326
- : ttoct_fun (ttoct/& A B) (ttoct/& C D) OUT
<- ttoct_fun A C EQ1
<- ttoct_fun B D EQ2
<- ceqtyp_& EQ1 EQ2 OUT.
327
- : ttoct_gimme_at W A A’ (ttoct/at D) <- ttoct_gimme A A’ D.
ttoct_gimme_sh : {A:world -> ty} {A’:world -> ctyp} ttoct (sh A) (csh A’) -> type.
%mode ttoct_gimme_sh +A -A’ -D.
tocps- : {M : exp}
{WM : of M A W}
{CT : ttoct A CA}
% this term represents the result of conversion.
% it takes a continuation with a hole for the
% translated result and fills it in.
{CC : (cval -> cexp) -> cexp}
% this derivation types the result of conversion:
% All well-formed instantiations of CC are well-formed.
% For C to be a well-formed instantiation, it must
% itself be well-typed for all appropriate arguments.
({C : cval -> cexp}
({cv : cval}
{wcv : cofv cv CA W}
cof (C cv) W) ->
% result of course can depend on C
cof (CC C) W) ->
type.
%mode tocps- +M +WM -CT -CC -WCC.
328
cof (CC C) W)}
type.
%mode wcc_resp +WCC +EQ -WCC’.
wcc_resp_ : wcc_resp D ceqtyp_ D.
% forward declared
% tocpsvv- : {WV : ofvv V A}
% {CT : ttoctf A CA}
% {WCV : cofvv CV CA}
% type.
%mode tocpsvv- +WV -CT -WCV.
329
FM _ ([f][wf]
FN _ ([a][wa]
co_call wf (cov_pair wa (cov_lam ([r][wr] wc r wr)) ))))
<- ttoct_gimme (A => B) (A’ c& (B’ ccont) ccont) (ttoct/=> CTA CTB)
<- tocps+ M WM (ttoct/=> CTA CTB) _ FM
<- tocps+ N WN CTA _ FN.
% interesting
c_get : tocps- (get W MA M) (ofget MOB WMA WM) CT _
([c][wc]
% eval remote addr
FMA _ ([a][wa]
% get our addr
co_localhost [h][wh]
% make it valid
co_put cmob_addr wh [uh][wuh]
% head on over...
co_go wa
(FM _ ([m][wm]
% make result valid
co_put CMOB wm [um][wum]
% and go back...
co_go (cov_valid wuh) (wc (cvalid um) (cov_valid wum))))))
<- ttoct_gimme A A’ CT
<- tocps+ MA WMA ttoct/addr _ FMA
<- tocps+ M WM CT _ FM
<- cmobmob CT MOB CMOB.
330
<- tocps+ M WM (ttoct/sh CTA) _ FM
<- ttoct_gimme B B’ CTN
<- ( {x}{xof : ofvv x Af}
{x’}{x’of : cofvv x’ Af’}
{thm:tocpsvv- xof (ttoctf/ CTA) x’of}
cv_pair : tocpsv- (&Iv WV1 WV2) (ttoct/& CT1 CT2) (cov_pair WV1’ WV2’)
<- tocpsv- WV1 CT1 WV1’
<- tocpsv- WV2 CT2 WV2’.
331
<- tocpsvv- WVV (ttoctf/ CTA) WVV’.
% continuation expressions
ccfst : ccval -> (ccval -> ccexp) -> ccexp.
ccsnd : ccval -> (ccval -> ccexp) -> ccexp.
cclocalhost : (ccval -> ccexp) -> ccexp.
cclets : ccval -> (ccvval -> ccexp) -> ccexp.
ccput : ccval -> (ccvval -> ccexp) -> ccexp.
ccleta : ccval -> (ccval -> ccexp) -> ccexp.
ccunpack : ccval -> (world -> ccval -> ccexp) -> ccexp.
ccwapp : ccval -> world -> (ccval -> ccexp) -> ccexp.
% continuation values
ccpair : ccval -> ccval -> ccval.
ccheld : ccval -> ccval.
% argument -> environment -> body env
ccclosure : (ccval -> ccval -> ccexp) -> ccval -> ccval.
ccconst : world -> ccval.
ccwlam : (world -> ccval) -> ccval.
ccpack : world -> ccval -> ccval.
cc1 : ccval.
ccvalid : ccvval -> ccval.
ccsh : ccvval -> ccval.
ccvv : (world -> ccval) -> ccvval.
332
%worlds (blockw | blockccv | blockccvv) (ccval) (ccvval) (ccexp).
333
fvv/snd : frozenvv ([x] ccsnd (V x) ([y] C y x))
<- ({y} frozenvv (C y))
<- vfrozenvv V.
fvv/localhost : frozenvv ([x] cclocalhost ([u] C u x))
<- ({y} frozenvv (C y)).
fvv/lets : frozenvv ([x] cclets (V x) ([u] C u x))
<- ({u} frozenvv (C u))
<- vfrozenvv V.
fvv/put : frozenvv ([x] ccput (V x) ([u] C u x))
<- ({u} frozenvv (C u))
<- vfrozenvv V.
fvv/leta : frozenvv ([x] ccleta (V x) ([y] C y x))
<- ({y} frozenvv (C y))
<- vfrozenvv V.
fvv/unpack : frozenvv ([x] ccunpack (V x) ([w][y] C w y x))
<- ({w:world}{y:ccval} frozenvv (C w y))
<- vfrozenvv V.
fvv/wapp : frozenvv ([x] ccwapp (V x) W ([y] C y x))
<- ({y} frozenvv (C y))
<- vfrozenvv V.
fvv/go : frozenvv ([x] ccgo W (V1 x) (V2 x))
<- vfrozenvv V2
<- vfrozenvv V1.
fvv/call : frozenvv ([x] cccall (V1 x) (V2 x))
<- vfrozenvv V2
<- vfrozenvv V1.
fvv/halt : frozenvv ([x] cchalt).
vfvv/valid : vfrozenvv ([x] ccvalid (VV x))
<- vvfrozenvv VV.
vfvv/pair : vfrozenvv ([x] ccpair (V1 x) (V2 x))
<- vfrozenvv V2
<- vfrozenvv V1.
vfvv/held : vfrozenvv ([x] ccheld (V x)) <- vfrozenvv V.
vfvv/const : vfrozenvv ([x] ccconst W).
vfvv/1 : vfrozenvv ([x] cc1).
vfvv/wlam : vfrozenvv ([x] ccwlam ([w] V w x)) <- ({w} vfrozenvv (V w)).
vfvv/pack : vfrozenvv ([x] ccpack W (V x)) <- vfrozenvv V.
vfvv/closed : vfrozenvv ([u] V).
vfvv/closure : vfrozenvv ([x] ccclosure ([a][e] BOD a e) (ENV x))
<- vfrozenvv ENV.
vfvv/ch : vfrozenvv ([x] ccsh (VV x)) <- vvfrozenvv VV.
vvfvv/vv : vvfrozenvv ([x] ccvv ([w] V w x)) <- ({w} vfrozenvv (V w)).
vvfvv/var : vvfrozenvv ([u] u).
vvfvv/closed : vvfrozenvv ([u] VV).
%% end duplicated
334
ccof (N v) W) ->
frozenvv N ->
ccof (cclets V N) W.
cco_unpack :
ccofv V (cexists A) W ->
({w}{v} ccofv v (A w) W ->
ccof (N w v) W) ->
({w} frozen (N w)) ->
ccof (ccunpack V N) W.
% this is new!
ccov_closure : ccofv ENV ENVT W ->
({x}{xof : ccofv x A W}
{e}{eof : ccofv e ENVT W}
ccof (BOD x e) W) ->
({x} frozen ([y] BOD x y)) ->
({y} frozen ([x] BOD x y)) ->
ccofv (ccclosure BOD ENV) (A ccont) W.
335
ccov_valid : ccofvv VV Af -> ccofv (ccvalid VV) (Af W) W.
ccov_ch : ccofvv VV A -> ccofv (ccsh VV) (csh A) W.
ccovv : ({w} ccofv (Vf w) (Af w) w) -> ccofvv (ccvv Vf) Af.
336
(f/localhost FN)
<- ({x} freeze (N x) (N’ x) (FN x)).
fz/valid : vfreeze ([v] ccvalid (VV v)) ([v] ccvalid (VV’ v))
(vf/valid FVV)
<- vvfreeze VV VV’ FVV.
fz/wlam : vfreeze ([x] ccwlam ([w] V w x)) ([x] ccwlam ([w] V’ w x))
(vf/wlam FV)
<- ({w} vfreeze (V w) (V’ w) (FV w)).
fz/ch : vfreeze ([v] ccsh (VV v)) ([v] ccsh (VV’ v)) (vf/ch FVV)
<- vvfreeze VV VV’ FVV.
fz/vv : vvfreeze ([v] ccvv ([w] V w v)) ([v] ccvv ([w] V’ w v)) (vvf/vv FVV)
<- ({w} vfreeze (V w) (V’ w) (FVV w)).
337
% mostly duplicated: same now for valid variables
% freeze a regular variable within an expression
freezevv : {N : ccvval -> ccexp}
{N’ : ccvval -> ccexp}
{F : frozenvv N’}
type.
%mode freezevv +D -D’ -F’.
338
fzvv/put : freezevv ([v] ccput (V v) ([xx] N xx v))
([v] ccput (V’ v) ([xx] N’ xx v))
(fvv/put FV FN)
<- vfreezevv V V’ FV
<- ({xx} freezevv (N xx) (N’ xx) (FN xx)).
fzvv/valid : vfreezevv ([v] ccvalid (VV v))
([v] ccvalid (VV’ v))
(vfvv/valid FVV)
<- vvfreezevv VV VV’ FVV.
fzvv/pair : vfreezevv ([v] ccpair (V1 v) (V2 v))
([v] ccpair (V1’ v) (V2’ v))
(vfvv/pair FV1 FV2)
<- vfreezevv V1 V1’ FV1
<- vfreezevv V2 V2’ FV2.
fzvv/held : vfreezevv ([v] ccheld (V v))
([v] ccheld (V’ v))
(vfvv/held FV)
<- vfreezevv V V’ FV.
fzvv/const : vfreezevv ([v] ccconst W) ([v] ccconst W) vfvv/const.
fzvv/1 : vfreezevv ([v] cc1) ([v] cc1) vfvv/1.
fzvv/wlam : vfreezevv ([x] ccwlam ([w] V w x))
([x] ccwlam ([w] V’ w x))
(vfvv/wlam FV)
<- ({w} vfreezevv (V w) (V’ w) (FV w)).
fzvv/pack : vfreezevv ([v] ccpack W (V v))
([v] ccpack W (V’ v))
(vfvv/pack FV)
<- vfreezevv V V’ FV.
fzvv/closedvv : vvfreezevv ([v] V) ([v] V) vvfvv/closed.
fzvv/ch : vfreezevv ([v] ccsh (VV v)) ([v] ccsh (VV’ v)) (vfvv/ch FVV)
<- vvfreezevv VV VV’ FVV.
fzvv/vv : vvfreezevv ([v] ccvv ([w] V w v)) ([v] ccvv ([w] V’ w v)) (vvfvv/vv FVV)
<- ({w} vfreezevv (V w) (V’ w) (FVV w)).
fzvv/closed : vfreezevv ([vv] V) ([vv] V) vfvv/closed.
fzvv/var : vvfreezevv ([u] u) ([u] u) vvfvv/var.
%% end duplicated
339
frozen-resp : ({x} ccexp-eq (V x) (V’ x)) -> frozen V -> frozen V’ -> type.
%mode frozen-resp +E +F -F’.
- : frozen-resp ([_] ccexp-eq/) F F.
vfrozen-resp : ({x} ccval-eq (V x) (V’ x)) -> vfrozen V -> vfrozen V’ -> type.
%mode vfrozen-resp +E +F -F’.
- : vfrozen-resp ([_] ccval-eq/) F F.
vvfrozen-resp : ({x} ccvval-eq (V x) (V’ x)) -> vvfrozen V -> vvfrozen V’ -> type.
%mode vvfrozen-resp +E +F -F’.
- : vvfrozen-resp ([_] ccvval-eq/) F F.
frozenvv-resp : ({x} ccexp-eq (V x) (V’ x)) -> frozenvv V -> frozenvv V’ -> type.
%mode frozenvv-resp +E +F -F’.
- : frozenvv-resp ([_] ccexp-eq/) F F.
vfrozenvv-resp : ({x} ccval-eq (V x) (V’ x)) -> vfrozenvv V -> vfrozenvv V’ -> type.
%mode vfrozenvv-resp +E +F -F’.
- : vfrozenvv-resp ([_] ccval-eq/) F F.
vvfrozenvv-resp : ({x} ccvval-eq (V x) (V’ x)) -> vvfrozenvv V -> vvfrozenvv V’ -> type.
%mode vvfrozenvv-resp +E +F -F’.
- : vvfrozenvv-resp ([_] ccvval-eq/) F F.
340
ccexp-eq (ccput V N) (ccput V’ N’) -> type.
%mode ccput-resp +D1 +D -E.
- : ccput-resp ccval-eq/ ([_] ccexp-eq/) ccexp-eq/.
341
- : ccclosure-resp ([_][_] ccexp-eq/) ccval-eq/ ccval-eq/.
% Since equalities are so trivial to prove, we can prove very strong ones:
ccf4-resp : ({a}{b}{c}{d} ccexp-eq (N a b c d) (N’ a b c d)) ->
{F : (ccval -> ccval -> ccval -> ccval -> ccexp) -> ccval}
ccval-eq (F N) (F N’) -> type.
%mode ccf4-resp +D +F -D’.
- : ccf4-resp ([_] [_] [_] [_] ccexp-eq/) _ ccval-eq/.
342
%total D (ccbind-resp D _ _).
%total D (ccubind-resp D _ _).
%total D (cc2val-resp D _ _ _).
%total D (cc2valv-resp D _ _ _).
%total D (ccuv-resp D _ _ _).
%total D (ccsh-resp D _).
%total D (ccf-resp D _ _).
%total D (ccf4-resp D _ _).
%total D (ccfu3-resp D _ _).
%total D (ccfuu2-resp D _ _).
%total D (ccclosure-resp D _ _).
%total D (ccwlam-resp D _).
%total D (ccvalid-resp D _).
%total D (ccvalubind-resp D _ _ _).
%total D (ccvv-resp D _).
343
- : permavclosed ([_] fz/1) ([_][_] ccval-eq/).
- : permavclosed ([_] fz/const) ([_][_] ccval-eq/).
- : permavclosed ([x] fz/ch (F x)) EQ’
<- permavvclosed F EQ
<- ({a} {x} ccsh-resp (EQ a x) (EQ’ a x)).
- : permavclosed ([x] fz/pack (F x)) EQ’
<- permavclosed F EQ
<- ({a} {x} cc2valv-resp (EQ a x) (EQ a x) ([1][2] ccpack W 1) (EQ’ a x)).
- : permavclosed ([x] fz/wlam ([w] F x w)) EQ’
<- ({w} permavclosed ([x] F x w) ([a][x] EQ a x w))
<- ({a} {x} ccwlam-resp ([w] EQ a x w) (EQ’ a x)).
- : permavclosed ([x] fz/valid (FF x)) EQ’
<- permavvclosed FF EQ
<- ({a} {x} ccvalid-resp (EQ a x) (EQ’ a x)).
- : permavvclosed ([x] fz/vv ([w] F x w)) EQ’
<- ({w} permavclosed ([x] F x w) ([a][x] EQ a x w))
<- ({a} {x} ccvv-resp ([w] EQ a x w) (EQ’ a x)).
- : permavvclosed ([_] fz/vclosed) ([_][_] ccvval-eq/).
344
<- ({y} vvpermaclosed ([x] FN x y) ([v][x] EQN v x y))
<- ({a} {x} ccvalbind-resp (EQV a x) ([y] EQN a x y) ccleta (EQ’ a x)).
- : vvpermaclosed ([x] fz/unpack ([w] FN w x) (FV x)) EQ’
<- vvpermavclosed FV EQV
<- ({w:world} {y} vvpermaclosed ([x] FN w x y) ([v][x] EQN w v x y))
<- ({a} {x} ccunpack-resp (EQV a x) ([w][y] EQN w a x y) (EQ’ a x)).
- : vvpermaclosed ([x] fz/wapp (FN x) (FV x)) EQ’
<- vvpermavclosed FV EQV
<- ({y} vvpermaclosed ([x] FN x y) ([v][x] EQN v x y))
<- ({a} {x} ccvalbind-resp (EQV a x) ([y] EQN a x y) ([a][x] ccwapp a W x) (EQ’ a x)).
- : vvpermaclosed ([x] fz/fst (FN x) (FV x)) EQ’
<- vvpermavclosed FV EQV
<- ({y} vvpermaclosed ([x] FN x y) ([v][x] EQN v x y))
<- ({a} {x} ccvalbind-resp (EQV a x) ([y] EQN a x y) ccfst (EQ’ a x)).
- : vvpermaclosed ([x] fz/snd (FN x) (FV x)) EQ’
<- vvpermavclosed FV EQV
<- ({y} vvpermaclosed ([x] FN x y) ([v][x] EQN v x y))
<- ({a} {x} ccvalbind-resp (EQV a x) ([y] EQN a x y) ccsnd (EQ’ a x)).
- : vvpermaclosed ([x] fz/go (FA x) (FV x)) EQ’
<- vvpermavclosed FA EQA
<- vvpermavclosed FV EQV
<- ({a} {x} cc2val-resp (EQA a x) (EQV a x) ([a][b] ccgo W b a) (EQ’ a x)).
- : vvpermavclosed ([_] fz/var) ([_][_] ccval-eq/).
- : vvpermavclosed ([_] fz/closed) ([_][_] ccval-eq/).
- : vvpermavclosed ([x] fz/pair (F2 x) (F1 x)) EQ’
<- vvpermavclosed F1 EQ1
<- vvpermavclosed F2 EQ2
<- ({a} {x} cc2valv-resp (EQ1 a x) (EQ2 a x) ccpair (EQ’ a x)).
- : vvpermavclosed ([x] fz/held (F x)) EQ’
<- vvpermavclosed F EQ
<- ({a} {x} cc2valv-resp (EQ a x) (EQ a x) ([1][2] ccheld 1) (EQ’ a x)).
- : vvpermavclosed ([_] fz/1) ([_][_] ccval-eq/).
- : vvpermavclosed ([_] fz/const) ([_][_] ccval-eq/).
- : vvpermavclosed ([x] fz/ch (F x)) EQ’
<- vvpermavvclosed F EQ
<- ({a} {x} ccsh-resp (EQ a x) (EQ’ a x)).
- : vvpermavclosed ([x] fz/pack (F x)) EQ’
<- vvpermavclosed F EQ
<- ({a} {x} cc2valv-resp (EQ a x) (EQ a x) ([1][2] ccpack W 1) (EQ’ a x)).
- : vvpermavclosed ([x] fz/wlam ([w] F x w)) EQ’
<- ({w} vvpermavclosed ([x] F x w) ([a][x] EQ a x w))
<- ({a} {x} ccwlam-resp ([w] EQ a x w) (EQ’ a x)).
- : vvpermavclosed ([x] fz/valid (FF x)) EQ’
<- vvpermavvclosed FF EQ
<- ({a} {x} ccvalid-resp (EQ a x) (EQ’ a x)).
- : vvpermavvclosed ([x] fz/vv ([w] F x w)) EQ’
<- ({w} vvpermavclosed ([x] F x w) ([a][x] EQ a x w))
<- ({a} {x} ccvv-resp ([w] EQ a x w) (EQ’ a x)).
- : vvpermavvclosed ([_] fz/vclosed) ([_][_] ccvval-eq/).
- : vvpermavclosed ([x] fz/closure (FE x) (FB x)) EQ’
<- vvpermavclosed FE ([a][x] (EQE a x) : ccval-eq (ENV’’ x) (ENV’ a x))
<- ({toss} {x} cc2valv-resp (ccval-eq/ : ccval-eq x _) (EQE toss x) ([y][e] ccpair (ccheld y) e)
(EQE2 toss x))
<- ({arg} {envtail}
vvpermaclosed ([x] FB x arg envtail)
([toss] [x] EQB toss x arg envtail
: ccexp-eq (BOD’’ x arg envtail) (BOD’ toss x arg envtail)))
<- ({arg} {envtail} {toss} {exh}
ccvalbind-resp (ccval-eq/ : ccval-eq cc1 cc1)
([ex] EQB toss ex arg envtail :
ccexp-eq (BOD’’ ex arg envtail) (BOD’ toss ex arg envtail))
([_][n] ccleta exh n) (EQB2 arg envtail toss exh))
<- ({arg} {toss} {exh} {e}
ccvalbind-resp (ccval-eq/ : ccval-eq cc1 cc1)
([envtail] EQB2 arg envtail toss exh) ([_][n] ccsnd e n) (EQB3 arg toss exh e))
<- ({arg} {toss} {e}
345
ccvalbind-resp (ccval-eq/ : ccval-eq cc1 cc1) ([exh] EQB3 arg toss exh e)
([_][n] ccfst e n) (EQB4 arg toss e))
<- ({toss} {x}
ccclosure-resp ([arg][e] EQB4 arg toss e) (EQE2 toss x) (EQ’ toss x)).
346
- : permaclosedvv ([x] fzvv/fst (FN x) (FV x)) EQ’
<- permavclosedvv FV EQV
<- ({y} permaclosedvv ([x] FN x y) ([v][x] EQN v x y))
<- ({a} {x} ccvalbind-resp (EQV a x) ([y] EQN a x y) ccfst (EQ’ a x)).
- : permaclosedvv ([x] fzvv/snd (FN x) (FV x)) EQ’
<- permavclosedvv FV EQV
<- ({y} permaclosedvv ([x] FN x y) ([v][x] EQN v x y))
<- ({a} {x} ccvalbind-resp (EQV a x) ([y] EQN a x y) ccsnd (EQ’ a x)).
- : permaclosedvv ([x] fzvv/go (FA x) (FV x)) EQ’
<- permavclosedvv FA EQA
<- permavclosedvv FV EQV
<- ({a} {x} cc2val-resp (EQA a x) (EQV a x) ([a][b] ccgo W b a) (EQ’ a x)).
- : permavclosedvv ([_] fzvv/closed) ([_][_] ccval-eq/).
- : permavclosedvv ([x] fzvv/pair (F2 x) (F1 x)) EQ’
<- permavclosedvv F1 EQ1
<- permavclosedvv F2 EQ2
<- ({a} {x} cc2valv-resp (EQ1 a x) (EQ2 a x) ccpair (EQ’ a x)).
- : permavclosedvv ([x] fzvv/held (F x)) EQ’
<- permavclosedvv F EQ
<- ({a} {x} cc2valv-resp (EQ a x) (EQ a x) ([1][2] ccheld 1) (EQ’ a x)).
- : permavclosedvv ([_] fzvv/1) ([_][_] ccval-eq/).
- : permavclosedvv ([_] fzvv/const) ([_][_] ccval-eq/).
- : permavclosedvv ([x] fzvv/ch (F x)) EQ’
<- permavvclosedvv F EQ
<- ({a} {x} ccsh-resp (EQ a x) (EQ’ a x)).
- : permavclosedvv ([x] fzvv/pack (F x)) EQ’
<- permavclosedvv F EQ
<- ({a} {x} cc2valv-resp (EQ a x) (EQ a x) ([1][2] ccpack W 1) (EQ’ a x)).
- : permavclosedvv ([x] fzvv/wlam ([w] F x w)) EQ’
<- ({w} permavclosedvv ([x] F x w) ([a][x] EQ a x w))
<- ({a} {x} ccwlam-resp ([w] EQ a x w) (EQ’ a x)).
- : permavclosedvv ([x] fzvv/valid (FF x)) EQ’
<- permavvclosedvv FF EQ
<- ({a} {x} ccvalid-resp (EQ a x) (EQ’ a x)).
- : permavvclosedvv ([x] fzvv/vv ([w] F x w)) EQ’
<- ({w} permavclosedvv ([x] F x w) ([a][x] EQ a x w))
<- ({a} {x} ccvv-resp ([w] EQ a x w) (EQ’ a x)).
%% end duplication; fzvv/closure is different here
347
<-({a} {x} cclocalhost-resp ([y] EQ a x y) (EQ’ a x)).
- :vvpermaclosedvv ([x] fzvv/call (FA x) (FV x)) EQ’
<-vvpermavclosedvv FA EQA
<-vvpermavclosedvv FV EQV
<-({a} {x} cc2val-resp (EQV a x) (EQA a x) cccall (EQ’ a x)).
- :vvpermaclosedvv ([x] fzvv/put (FN x) (FV x)) EQ’
<-vvpermavclosedvv FV EQV
<-({y} vvpermaclosedvv ([x] FN x y) ([v][x] EQN v x y))
<-({a} {x} ccput-resp (EQV a x) ([y] EQN a x y) (EQ’ a x)).
- :vvpermaclosedvv ([x] fzvv/lets (FN x) (FV x)) EQ’
<-vvpermavclosedvv FV EQV
<-({y} vvpermaclosedvv ([x] FN x y) ([v][x] EQN v x y))
<-({a} {x} cclets-resp (EQV a x) ([y] EQN a x y) (EQ’ a x)).
- :vvpermaclosedvv ([x] fzvv/leta (FN x) (FV x)) EQ’
<-vvpermavclosedvv FV EQV
<-({y} vvpermaclosedvv ([x] FN x y) ([v][x] EQN v x y))
<-({a} {x} ccvalbind-resp (EQV a x) ([y] EQN a x y) ccleta (EQ’ a x)).
- :vvpermaclosedvv ([x] fzvv/unpack ([w] FN w x) (FV x)) EQ’
<-vvpermavclosedvv FV EQV
<-({w:world} {y} vvpermaclosedvv ([x] FN w x y) ([v][x] EQN w v x y))
<-({a} {x} ccunpack-resp (EQV a x) ([w][y] EQN w a x y) (EQ’ a x)).
- :vvpermaclosedvv ([x] fzvv/wapp (FN x) (FV x)) EQ’
<-vvpermavclosedvv FV EQV
<-({y} vvpermaclosedvv ([x] FN x y) ([v][x] EQN v x y))
<-({a} {x} ccvalbind-resp (EQV a x) ([y] EQN a x y) ([a][x] ccwapp a W x) (EQ’ a x)).
- :vvpermaclosedvv ([x] fzvv/fst (FN x) (FV x)) EQ’
<-vvpermavclosedvv FV EQV
<-({y} vvpermaclosedvv ([x] FN x y) ([v][x] EQN v x y))
<-({a} {x} ccvalbind-resp (EQV a x) ([y] EQN a x y) ccfst (EQ’ a x)).
- :vvpermaclosedvv ([x] fzvv/snd (FN x) (FV x)) EQ’
<-vvpermavclosedvv FV EQV
<-({y} vvpermaclosedvv ([x] FN x y) ([v][x] EQN v x y))
<-({a} {x} ccvalbind-resp (EQV a x) ([y] EQN a x y) ccsnd (EQ’ a x)).
- :vvpermaclosedvv ([x] fzvv/go (FA x) (FV x)) EQ’
<-vvpermavclosedvv FA EQA
<-vvpermavclosedvv FV EQV
<-({a} {x} cc2val-resp (EQA a x) (EQV a x) ([a][b] ccgo W b a) (EQ’ a x)).
- :vvpermavclosedvv ([x] fzvv/pair (F2 x) (F1 x)) EQ’
<-vvpermavclosedvv F1 EQ1
<-vvpermavclosedvv F2 EQ2
<-({a} {x} cc2valv-resp (EQ1 a x) (EQ2 a x) ccpair (EQ’ a x)).
- :vvpermavclosedvv ([x] fzvv/held (F x)) EQ’
<-vvpermavclosedvv F EQ
<-({a} {x} cc2valv-resp (EQ a x) (EQ a x) ([1][2] ccheld 1) (EQ’ a x)).
- :vvpermavclosedvv ([_] fzvv/1) ([_][_] ccval-eq/).
- :vvpermavclosedvv ([_] fzvv/const) ([_][_] ccval-eq/).
- :vvpermavclosedvv ([x] fzvv/ch (F x)) EQ’
<-vvpermavvclosedvv F EQ
<-({a} {x} ccsh-resp (EQ a x) (EQ’ a x)).
- :vvpermavclosedvv ([x] fzvv/pack (F x)) EQ’
<-vvpermavclosedvv F EQ
<-({a} {x} cc2valv-resp (EQ a x) (EQ a x) ([1][2] ccpack W 1) (EQ’ a x)).
- :vvpermavclosedvv ([x] fzvv/wlam ([w] F x w)) EQ’
<-({w} vvpermavclosedvv ([x] F x w) ([a][x] EQ a x w))
<-({a} {x} ccwlam-resp ([w] EQ a x w) (EQ’ a x)).
- :vvpermavclosedvv ([x] fzvv/valid (FF x)) EQ’
<-vvpermavvclosedvv FF EQ
<-({a} {x} ccvalid-resp (EQ a x) (EQ’ a x)).
- :vvpermavvclosedvv ([x] fzvv/vv ([w] F x w)) EQ’
<-({w} vvpermavclosedvv ([x] F x w) ([a][x] EQ a x w))
<-({a} {x} ccvv-resp ([w] EQ a x w) (EQ’ a x)).
- :vvpermavclosedvv ([x] fzvv/closure (FE x) (FB x)) EQ’
<-vvpermavclosedvv FE ([a][x] (EQE a x) : ccval-eq (ENV’’ x) (ENV’ a x))
<-({toss} {x} ccuv-resp (ccvval-eq/ : ccvval-eq x _) (EQE toss x) ([y][e] ccpair (ccsh y) e)
(EQE2 toss x))
<- ({arg} {envtail}
348
vvpermaclosedvv ([x] FB x arg envtail)
([toss] [x] EQB toss x arg envtail :
ccexp-eq (BOD’’ x arg envtail) (BOD’ toss x arg envtail)))
<- ({arg} {envtail} {toss} {exh}
ccubind-resp ([ex] EQB toss ex arg envtail : ccexp-eq (BOD’’ ex arg envtail)
(BOD’ toss ex arg envtail)) ([n] cclets exh n) (EQB2 arg envtail toss exh))
<- ({arg} {toss} {exh} {e}
ccbind-resp ([envtail] EQB2 arg envtail toss exh) ([n] ccsnd e n) (EQB3 arg toss exh e))
<- ({arg} {toss} {e}
ccbind-resp ([exh] EQB3 arg toss exh e) ([n] ccfst e n) (EQB4 arg toss e))
<- ({toss} {x}
ccclosure-resp ([arg][e] EQB4 arg toss e) (EQE2 toss x) (EQ’ toss x)).
- : vvpermavclosedvv ([v] fzvv/closed) ([_][_] ccval-eq/).
- : vvpermavvclosedvv ([v] fzvv/closedvv) ([_][_] ccvval-eq/).
- : vvpermavvclosedvv ([v] fzvv/var) ([_][_] ccvval-eq/).
349
<- ({v} vvpermafrost ([x] ZN x v) ([x] FN x v) ([x] ZN’ x v)).
- : vvpermafrost ([x] fvv/put (ZV x) (ZN x)) ([x] fz/put (FN x) (FV x))
([x] fvv/put (ZV’ x) (ZN’ x))
<- vvpermavfrost ZV FV ZV’
<- ({vv} vvpermafrost ([x] ZN x vv) ([x] FN x vv) ([x] ZN’ x vv)).
- : vvpermafrost ([x] fvv/lets (ZV x) (ZN x)) ([x] fz/lets (FN x) (FV x))
([x] fvv/lets (ZV’ x) (ZN’ x))
<- vvpermavfrost ZV FV ZV’
<- ({vv} vvpermafrost ([x] ZN x vv) ([x] FN x vv) ([x] ZN’ x vv)).
- : vvpermafrost ([x] fvv/localhost (ZN x)) ([x] fz/localhost (FN x))
([x] fvv/localhost (ZN’ x))
<- ({v} vvpermafrost ([x] ZN x v) ([x] FN x v) ([x] ZN’ x v)).
- : vvpermafrost ([x] fvv/leta (ZV x) (ZN x)) ([x] fz/leta (FN x) (FV x))
([x] fvv/leta (ZV’ x) (ZN’ x))
<- vvpermavfrost ZV FV ZV’
<- ({v} vvpermafrost ([x] ZN x v) ([x] FN x v) ([x] ZN’ x v)).
- : vvpermafrost ([x] fvv/unpack (ZV x) (ZN x)) ([x] fz/unpack (FN x) (FV x))
([x] fvv/unpack (ZV’ x) (ZN’ x))
<- vvpermavfrost ZV FV ZV’
<- ({w}{v} vvpermafrost ([x] ZN x w v) ([x] FN x w v) ([x] ZN’ x w v)).
- : vvpermafrost ([x] fvv/wapp (ZV x) (ZN x)) ([x] fz/wapp (FN x) (FV x))
([x] fvv/wapp (ZV’ x) (ZN’ x))
<- vvpermavfrost ZV FV ZV’
<- ({v} vvpermafrost ([x] ZN x v) ([x] FN x v) ([x] ZN’ x v)).
- : vvpermafrost ([x] fvv/go (Z1 x) (Z2 x)) ([x] fz/go (F2 x) (F1 x))
([x] fvv/go (Z1’ x) (Z2’ x))
<- vvpermavfrost Z1 F1 Z1’
<- vvpermavfrost Z2 F2 Z2’.
- : vvpermavfrost ([x] vfvv/ch (Z x)) ([x] fz/ch (FF x)) ([x] vfvv/ch (Z’ x))
<- vvpermavvfrost Z FF Z’.
- : vvpermavfrost ([x] vfvv/pair (Z2 x) (Z1 x)) ([x] fz/pair (F1 x) (F2 x))
([x] vfvv/pair (Z2’ x) (Z1’ x))
<- vvpermavfrost Z1 F1 Z1’
<- vvpermavfrost Z2 F2 Z2’.
- : vvpermavfrost Z ([_] fz/closed) Z.
- : vvpermavfrost Z ([_] fz/1) Z.
- : vvpermavfrost ([_] vfvv/const) ([v] fz/const) ([_] vfvv/const).
- : vvpermavfrost ([v] vfvv/held (Z v)) ([v] fz/held (F v)) ([v] vfvv/held (Z’ v))
<- vvpermavfrost Z F Z’.
- : vvpermavfrost ([v] vfvv/wlam ([w:world] Z v w)) ([v] fz/wlam ([w] F v w))
([v] vfvv/wlam ([w] Z’ v w))
<- ({w:world} vvpermavfrost ([v] Z v w) ([v] F v w) ([v] Z’ v w)).
- : vvpermavfrost ([v] vfvv/pack (Z v)) ([v] fz/pack (F v)) ([v] vfvv/pack (Z’ v))
<- vvpermavfrost Z F Z’.
- : vvpermavfrost ([_] vfvv/closed) F Z’
<- vvpermavclosed F EQ
<- ({x} vfrozenvv-resp ([y] EQ y x) vfvv/closed (Z’ x)).
- : vvpermavfrost ([v] vfvv/valid (Z v)) ([v] fz/valid (F v)) ([v] vfvv/valid (Z’ v))
350
<- vvpermavvfrost Z F Z’.
%% another duplication
vvpermafrostvv :
{ZN : {v:ccvval} frozenvv ([y:ccvval] N v y)}
{FN : {y:ccvval} freezevv ([v:ccvval] N v y) ([v:ccvval] N’ v y) (F y)}
{ZN’: {v:ccvval} frozenvv ([y:ccvval] N’ v y)} type.
%mode vvpermafrostvv +ZN +FN -ZN’.
vvpermavfrostvv :
{ZN : {v:ccvval} vfrozenvv ([y:ccvval] N y v)}
{F2 : {y:ccvval} vfreezevv ([v:ccvval] N y v) ([v:ccvval] N’ v y) (F y)}
{ZN’: {v:ccvval} vfrozenvv ([y:ccvval] N’ v y)} type.
%mode vvpermavfrostvv +ZN +FN -ZN’.
vvpermavvfrostvv :
{ZN : {v:ccvval} vvfrozenvv ([y:ccvval] N y v)}
{F2 : {y:ccvval} vvfreezevv ([v:ccvval] N y v) ([v:ccvval] N’ v y) (F y)}
{ZN’: {v:ccvval} vvfrozenvv ([y:ccvval] N’ v y)} type.
%mode vvpermavvfrostvv +ZN +FN -ZN’.
351
- : vvpermafrostvv ([x] fvv/wapp (ZV x) (ZN x)) ([x] fzvv/wapp (FN x) (FV x))
([x] fvv/wapp (ZV’ x) (ZN’ x))
<- vvpermavfrostvv ZV FV ZV’
<- ({v} vvpermafrostvv ([x] ZN x v) ([x] FN x v) ([x] ZN’ x v)).
- : vvpermafrostvv ([x] fvv/go (Z1 x) (Z2 x)) ([x] fzvv/go (F2 x) (F1 x))
([x] fvv/go (Z1’ x) (Z2’ x))
<- vvpermavfrostvv Z1 F1 Z1’
<- vvpermavfrostvv Z2 F2 Z2’.
- : vvpermavfrostvv ([x] vfvv/ch (Z x)) ([x] fzvv/ch (FF x)) ([x] vfvv/ch (Z’ x))
<- vvpermavvfrostvv Z FF Z’.
- : vvpermavfrostvv ([x] vfvv/pair (Z2 x) (Z1 x)) ([x] fzvv/pair (F1 x) (F2 x))
([x] vfvv/pair (Z2’ x) (Z1’ x))
<- vvpermavfrostvv Z1 F1 Z1’
<- vvpermavfrostvv Z2 F2 Z2’.
- : vvpermavfrostvv Z ([_] fzvv/closed) Z.
- : vvpermavfrostvv Z ([_] fzvv/1) Z.
- : vvpermavfrostvv ([_] vfvv/const) ([v] fzvv/const) ([_] vfvv/const).
- : vvpermavfrostvv ([v] vfvv/held (Z v)) ([v] fzvv/held (F v)) ([v] vfvv/held (Z’ v))
<- vvpermavfrostvv Z F Z’.
- : vvpermavfrostvv ([v] vfvv/wlam ([w:world] Z v w)) ([v] fzvv/wlam ([w] F v w))
([v] vfvv/wlam ([w] Z’ v w))
<- ({w:world} vvpermavfrostvv ([v] Z v w) ([v] F v w) ([v] Z’ v w)).
- : vvpermavfrostvv ([v] vfvv/pack (Z v)) ([v] fzvv/pack (F v)) ([v] vfvv/pack (Z’ v))
<- vvpermavfrostvv Z F Z’.
- : vvpermavfrostvv ([_] vfvv/closed) F Z’
<- vvpermavclosedvv F EQ
<- ({x} vfrozenvv-resp ([y] EQ y x) vfvv/closed (Z’ x)).
- : vvpermavfrostvv ([v] vfvv/closure (Z v)) ([y:ccvval] fzvv/closure
(F1 y : vfreezevv ([v:ccvval] V1 v y)
([v:ccvval] ENV’ y v) (FZ y))
([a][e] F2 y a e)) ([v] Z’’ v)
<- vvpermavfrostvv Z F1 ZENV
<- ({a}{e} vvpermaclosedvv ([v] F2 v a e) ([utoss:ccvval][x] EQ utoss x a e))
<- ({toss:ccvval}{x:ccvval}
ccfuu2-resp EQ
([f4]
ccclosure ([arg][env]
ccfst env [exh]
ccsnd env [envtail]
cclets exh [ex]
f4 toss ex arg envtail) (ccpair (ccsh x) (ENV’ toss x)))
(EQ2 toss x))
<- ({x} vfrozenvv-resp ([y] EQ2 y x)
(vfvv/closure (vfvv/pair vfvv/closed (ZENV x))) (Z’’ x)).
- : vvpermavfrostvv ([v] vfvv/valid (Z v)) ([v] fzvv/valid (F v)) ([v] vfvv/valid (Z’ v))
<- vvpermavvfrostvv Z F Z’.
- : vvpermavvfrostvv ([v] vvfvv/vv ([w] Z v w)) ([v] fzvv/vv ([w] F v w))
([v] vvfvv/vv ([w] Z’ v w))
<- ({w} vvpermavfrostvv ([v] Z v w) ([v] F v w) ([v] Z’ v w)).
- : vvpermavvfrostvv Z ([_] fzvv/closedvv) Z.
- : vvpermavvfrostvv ([_] vvfvv/closed) F Z’
<- vvpermavvclosedvv F EQ
<- ({x} vvfrozenvv-resp ([y] EQ y x) vvfvv/closed (Z’ x)).
%% end duplication
352
<- permavfrost Z2 F2 Z2’.
- : permafrost ([x] f/put (ZV x) (ZN x)) ([x] fz/put (FN x) (FV x))
([x] f/put (ZV’ x) (ZN’ x))
<- permavfrost ZV FV ZV’
<- ({vv} permafrost ([x] ZN x vv) ([x] FN x vv) ([x] ZN’ x vv)).
- : permafrost ([x] f/lets (ZV x) (ZN x)) ([x] fz/lets (FN x) (FV x))
([x] f/lets (ZV’ x) (ZN’ x))
<- permavfrost ZV FV ZV’
<- ({vv} permafrost ([x] ZN x vv) ([x] FN x vv) ([x] ZN’ x vv)).
- : permafrost ([x] f/localhost (ZN x)) ([x] fz/localhost (FN x))
([x] f/localhost (ZN’ x))
<- ({v} permafrost ([x] ZN x v) ([x] FN x v) ([x] ZN’ x v)).
- : permafrost ([x] f/leta (ZV x) (ZN x)) ([x] fz/leta (FN x) (FV x))
([x] f/leta (ZV’ x) (ZN’ x))
<- permavfrost ZV FV ZV’
<- ({v} permafrost ([x] ZN x v) ([x] FN x v) ([x] ZN’ x v)).
- : permafrost ([x] f/fst (ZV x) (ZN x)) ([x] fz/fst (FN x) (FV x))
([x] f/fst (ZV’ x) (ZN’ x))
<- permavfrost ZV FV ZV’
<- ({v} permafrost ([x] ZN x v) ([x] FN x v) ([x] ZN’ x v)).
- : permafrost ([x] f/snd (ZV x) (ZN x)) ([x] fz/snd (FN x) (FV x))
([x] f/snd (ZV’ x) (ZN’ x))
<- permavfrost ZV FV ZV’
<- ({v} permafrost ([x] ZN x v) ([x] FN x v) ([x] ZN’ x v)).
- : permafrost ([x] f/unpack (ZV x) (ZN x)) ([x] fz/unpack (FN x) (FV x))
([x] f/unpack (ZV’ x) (ZN’ x))
<- permavfrost ZV FV ZV’
<- ({w}{v} permafrost ([x] ZN x w v) ([x] FN x w v) ([x] ZN’ x w v)).
- : permafrost ([x] f/wapp (ZV x) (ZN x)) ([x] fz/wapp (FN x) (FV x))
([x] f/wapp (ZV’ x) (ZN’ x))
<- permavfrost ZV FV ZV’
<- ({v} permafrost ([x] ZN x v) ([x] FN x v) ([x] ZN’ x v)).
- : permafrost ([x] f/go (Z1 x) (Z2 x)) ([x] fz/go (F2 x) (F1 x))
([x] f/go (Z1’ x) (Z2’ x))
<- permavfrost Z1 F1 Z1’
<- permavfrost Z2 F2 Z2’.
- : permavfrost ([x] vf/ch (Z x)) ([x] fz/ch (FF x)) ([x] vf/ch (Z’ x))
<- permavvfrost Z FF Z’.
- : permavfrost ([x] vf/pair (Z2 x) (Z1 x)) ([x] fz/pair (F1 x) (F2 x))
([x] vf/pair (Z2’ x) (Z1’ x))
<- permavfrost Z1 F1 Z1’
<- permavfrost Z2 F2 Z2’.
- : permafrost ([_] f/closed) F Z’
<- permaclosed F EQ
<- ({x} frozen-resp ([y] EQ y x) f/closed (Z’ x)).
- : permavfrost ([_] vf/closed) F Z’
<- permavclosed F EQ
<- ({x} vfrozen-resp ([y] EQ y x) vf/closed (Z’ x)).
- : permavfrost Z ([_] fz/closed) Z.
- : permavfrost Z ([_] fz/1) Z.
- : permavfrost ([_] vf/const) ([v] fz/const) ([_] vf/const).
- : permavfrost ([v] vf/held (Z v)) ([v] fz/held (F v)) ([v] vf/held (Z’ v))
<- permavfrost Z F Z’.
- : permavfrost ([v] vf/wlam ([w:world] Z v w)) ([v] fz/wlam ([w] F v w))
([v] vf/wlam ([w] Z’ v w))
<- ({w:world} permavfrost ([v] Z v w) ([v] F v w) ([v] Z’ v w)).
- : permavfrost ([v] vf/pack (Z v)) ([v] fz/pack (F v)) ([v] vf/pack (Z’ v))
<- permavfrost Z F Z’.
- : permavfrost ([v] vf/closure (Z v)) ([v] fz/closure (F1 v) ([a][e] F2 v a e)) ([v] Z’’ v)
<- permavfrost Z F1 ZENV
<- ({a}{e} permaclosed ([v] F2 v a e) ([toss][x] EQ x toss a e))
<- ({toss:ccval}{x}
ccf4-resp EQ
([f4]
ccclosure ([arg][env]
ccfst env [exh]
353
ccsnd env [envtail]
ccleta exh [ex]
f4 ex toss arg envtail) (ccpair (ccheld x) (ENV’ toss x)))
(EQ2 toss x))
<- ({x} vfrozen-resp ([y] EQ2 y x) (vf/closure (vf/pair vf/closed (ZENV x))) (Z’’ x)).
- : permavfrost ([v] vf/valid (Z v)) ([v] fz/valid (F v)) ([v] vf/valid (Z’ v))
<- permavvfrost Z F Z’.
- : permavvfrost ([v] vvf/vv ([w] Z v w)) ([v] fz/vv ([w] F v w)) ([v] vvf/vv ([w] Z’ v w))
<- ({w} permavfrost ([v] Z v w) ([v] F v w) ([v] Z’ v w)).
- : permavvfrost Z ([_] fz/vclosed) Z.
- : permavvfrost ([_] vvf/closed) F Z’
<- permavvclosed F EQ
<- ({x} vvfrozen-resp ([y] EQ y x) vvf/closed (Z’ x)).
permafrostvv :
{ZN : {u:ccvval} frozen ([v:ccval] N u v)}
{FN : {v:ccval} freezevv ([u:ccvval] N u v) ([u:ccvval] N’ u v) (F v)}
{ZN’: {u:ccvval} frozen ([v:ccval] N’ u v)} type.
%mode permafrostvv +ZN +FN -ZN’.
permavfrostvv :
{ZN : {u:ccvval} vfrozen ([v:ccval] N u v)}
{FN : {v:ccval} vfreezevv ([u:ccvval] N u v) ([u:ccvval] N’ u v) (F v)}
{ZN’: {u:ccvval} vfrozen ([v:ccval] N’ u v)} type.
%mode permavfrostvv +ZN +FN -ZN’.
permavvfrostvv :
{ZN : {u:ccvval} vvfrozen ([v:ccval] N u v)}
{FN : {v:ccval} vvfreezevv ([u:ccvval] N u v) ([u:ccvval] N’ u v) (F v)}
{ZN’: {u:ccvval} vvfrozen ([v:ccval] N’ u v)} type.
%mode permavvfrostvv +ZN +FN -ZN’.
354
- : permafrostvv ([x] f/unpack (ZV x) (ZN x)) ([x] fzvv/unpack (FN x) (FV x))
([x] f/unpack (ZV’ x) (ZN’ x))
<- permavfrostvv ZV FV ZV’
<- ({w}{v} permafrostvv ([x] ZN x w v) ([x] FN x w v) ([x] ZN’ x w v)).
- : permafrostvv ([x] f/wapp (ZV x) (ZN x)) ([x] fzvv/wapp (FN x) (FV x))
([x] f/wapp (ZV’ x) (ZN’ x))
<- permavfrostvv ZV FV ZV’
<- ({v} permafrostvv ([x] ZN x v) ([x] FN x v) ([x] ZN’ x v)).
- : permafrostvv ([x] f/go (Z1 x) (Z2 x)) ([x] fzvv/go (F2 x) (F1 x))
([x] f/go (Z1’ x) (Z2’ x))
<- permavfrostvv Z1 F1 Z1’
<- permavfrostvv Z2 F2 Z2’.
- : permavfrostvv ([x] vf/ch (Z x)) ([x] fzvv/ch (FF x)) ([x] vf/ch (Z’ x))
<- permavvfrostvv Z FF Z’.
- : permavfrostvv ([x] vf/pair (Z2 x) (Z1 x)) ([x] fzvv/pair (F1 x) (F2 x))
([x] vf/pair (Z2’ x) (Z1’ x))
<- permavfrostvv Z1 F1 Z1’
<- permavfrostvv Z2 F2 Z2’.
- : permafrostvv ([_] f/closed) F Z’
<- permaclosedvv F EQ
<- ({x} frozen-resp ([y] EQ y x) f/closed (Z’ x)).
- : permavfrostvv ([_] vf/closed) F Z’
<- permavclosedvv F EQ
<- ({x} vfrozen-resp ([y] EQ y x) vf/closed (Z’ x)).
- : permavfrostvv Z ([_] fzvv/closed) Z.
- : permavfrostvv Z ([_] fzvv/1) Z.
- : permavfrostvv ([_] vf/const) ([v] fzvv/const) ([_] vf/const).
- : permavfrostvv ([v] vf/held (Z v)) ([v] fzvv/held (F v)) ([v] vf/held (Z’ v))
<- permavfrostvv Z F Z’.
- : permavfrostvv ([v] vf/wlam ([w:world] Z v w)) ([v] fzvv/wlam ([w] F v w))
([v] vf/wlam ([w] Z’ v w))
<- ({w:world} permavfrostvv ([v] Z v w) ([v] F v w) ([v] Z’ v w)).
- : permavfrostvv ([v] vf/pack (Z v)) ([v] fzvv/pack (F v)) ([v] vf/pack (Z’ v))
<- permavfrostvv Z F Z’.
- : permavfrostvv ([v] vf/valid (Z v)) ([v] fzvv/valid (F v)) ([v] vf/valid (Z’ v))
<- permavvfrostvv Z F Z’.
- : permavvfrostvv ([v] vvf/vv ([w] Z v w)) ([v] fzvv/vv ([w] F v w))
([v] vvf/vv ([w] Z’ v w))
<- ({w} permavfrostvv ([v] Z v w) ([v] F v w) ([v] Z’ v w)).
%%% end duplicated
355
% freezing needs to preserve well-formedness.
- : vfreeze/ok D fz/var D.
- : vfreeze/ok D fz/closed D.
- : freeze/ok ([_][_] cco_halt) fz/halt ([_][_] cco_halt).
- : freeze/ok ([x][wx] cco_call (WF x wx) (WA x wx)) (fz/call FA FF)
([x][wx] cco_call (WF’ x wx) (WA’ x wx))
<- vfreeze/ok WF FF WF’
<- vfreeze/ok WA FA WA’.
- : freeze/ok ([x][wx] cco_go (WF x wx) (WA x wx)) (fz/go FA FF)
([x][wx] cco_go (WF’ x wx) (WA’ x wx))
<- vfreeze/ok WF FF WF’
<- vfreeze/ok WA FA WA’.
- : freeze/ok ([x][wx] cco_fst (WV x wx) ([y][wy] WN y wy x wx) (ZN x)) (fz/fst FN FV)
([x][wx] cco_fst (WV’ x wx) ([y][wy] WN’ y wy x wx) (ZN’ x))
<- vfreeze/ok WV FV WV’
<- ({x}{wx} freeze/ok (WN x wx) (FN x) (WN’ x wx))
<- permafrost ZN FN ZN’.
- : freeze/ok ([x][wx] cco_snd (WV x wx) ([y][wy] WN y wy x wx) (ZN x)) (fz/snd FN FV)
([x][wx] cco_snd (WV’ x wx) ([y][wy] WN’ y wy x wx) (ZN’ x))
<- vfreeze/ok WV FV WV’
<- ({x}{wx} freeze/ok (WN x wx) (FN x) (WN’ x wx))
<- permafrost ZN FN ZN’.
- : freeze/ok
([x][wx] cco_put MOB (WV x wx) ([yy][wyy:ccofvv yy ([_] A)] WN yy wyy x wx) (ZN x))
(fz/put FN FV) ([x][wx] cco_put MOB (WV’ x wx) ([yy][wyy] WN’ yy wyy x wx) (ZN’ x))
<- vfreeze/ok WV FV WV’
<- ({xx}{wxx} freeze/ok ([y][wy] WN xx wxx y wy) (FN xx) ([y][wy] WN’ xx wxx y wy))
<- vvpermafrost ZN FN ZN’.
- : freeze/ok ([x][wx] cco_lets (WV x wx) ([yy][wyy:ccofvv yy A] WN yy wyy x wx) (ZN x))
(fz/lets FN FV) ([x][wx] cco_lets (WV’ x wx) ([yy][wyy] WN’ yy wyy x wx) (ZN’ x))
<- vfreeze/ok WV FV WV’
<- ({xx}{wxx} freeze/ok ([y][wy] WN xx wxx y wy) (FN xx) ([y][wy] WN’ xx wxx y wy))
<- vvpermafrost ZN FN ZN’.
- : freeze/ok ([x][wx] cco_localhost ([y][wy] WN y wy x wx) (ZN x)) (fz/localhost FN)
([x][wx] cco_localhost ([y][wy] WN’ y wy x wx) (ZN’ x))
<- ({x}{wx} freeze/ok (WN x wx) (FN x) (WN’ x wx))
<- permafrost ZN FN ZN’.
- : freeze/ok ([x][wx] cco_leta (WV x wx) ([y][wy] WN y wy x wx) (ZN x)) (fz/leta FN FV)
([x][wx] cco_leta (WV’ x wx) ([y][wy] WN’ y wy x wx) (ZN’ x))
<- vfreeze/ok WV FV WV’
<- ({x}{wx} freeze/ok (WN x wx) (FN x) (WN’ x wx))
<- permafrost ZN FN ZN’.
- : freeze/ok
([x][wx] cco_unpack (WV x wx) ([w][y][wy] WN w y wy x wx) ([w] ZN w x)) (fz/unpack FN FV)
([x][wx] cco_unpack (WV’ x wx) ([w][y][wy] WN’ w y wy x wx) ([w] ZN’ w x))
<- vfreeze/ok WV FV WV’
356
<- ({w}{x}{wx} freeze/ok (WN w x wx) (FN w x) (WN’ w x wx))
<- ({w} permafrost (ZN w) (FN w) (ZN’ w)).
- : freeze/ok ([x][wx] cco_wapp (WV x wx : ccofv _ (call A) _)
([y][wy : ccofv y (A W’) W] WN y wy x wx) (ZN x)) (fz/wapp FN FV)
([x][wx] cco_wapp (WV’ x wx) ([y][wy] WN’ y wy x wx) (ZN’ x))
<- vfreeze/ok WV FV WV’
<- ({x}{wx} freeze/ok (WN x wx) (FN x) (WN’ x wx))
<- permafrost ZN FN ZN’.
- : vfreeze/ok ([x][wx] ccov_pair (W1 x wx) (W2 x wx)) (fz/pair F2 F1)
([x][wx] ccov_pair (W1’ x wx) (W2’ x wx))
<- vfreeze/ok W1 F1 W1’
<- vfreeze/ok W2 F2 W2’.
- : vfreeze/ok _ fz/1 ([x][wx] ccov_unit).
- : vfreeze/ok ([x][wx] ccov_ch (WVV x wx)) (fz/ch F) ([x][wx] ccov_ch (WVV’ x wx))
<- vvfreeze/ok WVV F WVV’.
- : vfreeze/ok
([x][wx:ccofv x AFREE W’]
ccov_closure (WENV x wx : ccofv (ENV x) AENV W)
([a][wa : ccofv a AARG W]
[e][we : ccofv e AENV W]
WBOD x wx a wa e we) (ZwrtENV x : {a} frozen ([env] BOD x a env))
(ZwrtARG x : {e} frozen ([arg] BOD x arg e)))
(fz/closure FENV ([a][e] FBOD a e : freeze _ ([x] BOD’ x a e) (ZBOD a e)))
%%
([x][wx:ccofv x AFREE W’]
ccov_closure (ccov_pair (ccov_held wx) (WENV’ x wx))
([a][wa : ccofv a AARG W]
[e][we : ccofv e ((AFREE cat W’) c& AENV) W]
cco_fst we
([exh][wexh:ccofv exh (AFREE cat W’) W]
cco_snd we
([envtail][wenvtail:ccofv envtail AENV W]
cco_leta wexh
([ex][wex:ccofv ex AFREE W’]
WBOD’ ex wex a wa envtail wenvtail
) % leta body
(ZBOD a envtail) % frozen wrt leta
) % snd body
(f/leta vf/closed
[y] ZwrtENV’ y a) % frozen wrt snd proj
) % fst body
(f/snd vf/closed [envtail]
f/leta vf/var [ex]
f/closed
) %{ frozen wrt fst proj }% )
([v]
f/fst vf/var [exh]
f/snd vf/var [envtail]
f/leta vf/closed [ex]
f/closed)
([v]
f/fst vf/closed [exh]
f/snd vf/closed [envtail]
f/leta vf/closed [ex]
ZwrtARG’ ex envtail))
%%
<- vfreeze/ok WENV FENV (WENV’ : {x}{ofx} ccofv (ENV’ x) AENV W)
<- ({a}{wa}{e}{we}
freeze/ok ([x][wx] WBOD x wx a wa e we) (FBOD a e)
([x][wx] WBOD’ x wx a wa e we : ccof (BOD’ x a e) W))
<- ({z} permafrost ([x] ZwrtARG x z) ([y] FBOD y z)
([x] ZwrtARG’ x z : frozen ([y] BOD’ x y z)))
<- ({y} permafrost ([x] ZwrtENV x y) ([z] FBOD y z)
([x] ZwrtENV’ x y : frozen ([z] BOD’ x y z))).
357
<- vfreeze/ok W F W’.
- : vfreeze/ok ([x][wx] ccov_wlam ([w] WV x wx w)) (fz/wlam ([w] F w))
([x][wx] ccov_wlam ([w] WV’ x wx w))
<- ({w} vfreeze/ok ([x][wx] WV x wx w) (F w) ([x][wx] WV’ x wx w)).
- : vfreeze/ok D fz/const D.
- : vfreeze/ok ([x][wx] ccov_held (W x wx)) (fz/held F) ([x][wx] ccov_held (W’ x wx))
<- vfreeze/ok W F W’.
- : vfreeze/ok ([x:ccval][wx:ccofv x A W] ccov_valid (WVV x wx : ccofvv _ Af))
(fz/valid F) ([x][wx] ccov_valid (WVV’ x wx))
<- vvfreeze/ok WVV F WVV’.
- : vvfreeze/ok ([x][wx] ccovv ([w] WV x wx w)) (fz/vv ([w] F w))
([x][wx] ccovv ([w] WV’ x wx w))
<- ({w} vfreeze/ok ([x][wx] WV x wx w) (F w) ([x][wx] WV’ x wx w)).
- : vvfreeze/ok D fz/vclosed D.
358
<- ({xx}{wxx} freezevv/ok ([y][wy] WN xx wxx y wy) (FN xx) ([y][wy] WN’ xx wxx y wy))
<- vvpermafrostvv ZN FN ZN’.
- : freezevv/ok ([x][wx] cco_localhost ([y][wy] WN y wy x wx) (ZN x)) (fzvv/localhost FN)
([x][wx] cco_localhost ([y][wy] WN’ y wy x wx) (ZN’ x))
<- ({x}{wx} freezevv/ok (WN x wx) (FN x) (WN’ x wx))
<- permafrostvv ZN FN ZN’.
- : freezevv/ok ([x][wx] cco_leta (WV x wx) ([y][wy] WN y wy x wx) (ZN x)) (fzvv/leta FN FV)
([x][wx] cco_leta (WV’ x wx) ([y][wy] WN’ y wy x wx) (ZN’ x))
<- vfreezevv/ok WV FV WV’
<- ({x}{wx} freezevv/ok (WN x wx) (FN x) (WN’ x wx))
<- permafrostvv ZN FN ZN’.
- : freezevv/ok ([x][wx] cco_unpack (WV x wx) ([w][y][wy] WN w y wy x wx) ([w] ZN w x))
(fzvv/unpack FN FV)
([x][wx] cco_unpack (WV’ x wx) ([w][y][wy] WN’ w y wy x wx) ([w] ZN’ w x))
<- vfreezevv/ok WV FV WV’
<- ({w}{x}{wx} freezevv/ok (WN w x wx) (FN w x) (WN’ w x wx))
<- ({w} permafrostvv (ZN w) (FN w) (ZN’ w)).
- : freezevv/ok ([x][wx] cco_wapp (WV x wx : ccofv _ (call A) _)
([y][wy : ccofv y (A W’) W] WN y wy x wx) (ZN x))
(fzvv/wapp FN FV) ([x][wx] cco_wapp (WV’ x wx) ([y][wy] WN’ y wy x wx) (ZN’ x))
<- vfreezevv/ok WV FV WV’
<- ({x}{wx} freezevv/ok (WN x wx) (FN x) (WN’ x wx))
<- permafrostvv ZN FN ZN’.
- : vfreezevv/ok ([x][wx] ccov_pair (W1 x wx) (W2 x wx)) (fzvv/pair F2 F1)
([x][wx] ccov_pair (W1’ x wx) (W2’ x wx))
<- vfreezevv/ok W1 F1 W1’
<- vfreezevv/ok W2 F2 W2’.
- : vfreezevv/ok _ fzvv/1 ([x][wx] ccov_unit).
- : vfreezevv/ok ([x][wx] ccov_ch (WVV x wx)) (fzvv/ch F) ([x][wx] ccov_ch (WVV’ x wx))
<- vvfreezevv/ok WVV F WVV’.
- : vfreezevv/ok ([x][wx] ccov_pack AF (W x wx)) (fzvv/pack F)
([x][wx] ccov_pack AF (W’ x wx))
<- vfreezevv/ok W F W’.
- : vfreezevv/ok ([x][wx] ccov_wlam ([w] WV x wx w)) (fzvv/wlam ([w] F w))
([x][wx] ccov_wlam ([w] WV’ x wx w))
<- ({w} vfreezevv/ok ([x][wx] WV x wx w) (F w) ([x][wx] WV’ x wx w)).
- : vfreezevv/ok D fzvv/const D.
- : vfreezevv/ok ([x][wx] ccov_held (W x wx)) (fzvv/held F) ([x][wx] ccov_held (W’ x wx))
<- vfreezevv/ok W F W’.
- : vfreezevv/ok ([x:ccvval][wx:ccofvv x Af] ccov_valid (WVV x wx : ccofvv _ Bf))
(fzvv/valid F) ([x][wx] ccov_valid (WVV’ x wx))
<- vvfreezevv/ok WVV F WVV’.
- : vvfreezevv/ok ([x][wx] ccovv ([w] WV x wx w)) (fzvv/vv ([w] F w))
([x][wx] ccovv ([w] WV’ x wx w))
<- ({w} vfreezevv/ok ([x][wx] WV x wx w) (F w) ([x][wx] WV’ x wx w)).
- : vfreezevv/ok D fzvv/closed D.
- : vfreezevv/ok
([x][wx:ccofvv x AFREE]
ccov_closure (WENV x wx : ccofv (ENV x) AENV W)
([a][wa : ccofv a AARG W]
[e][we : ccofv e AENV W]
WBOD x wx a wa e we) (ZwrtENV x : {a} frozen ([env] BOD x a env))
(ZwrtARG x : {e} frozen ([arg] BOD x arg e)))
(fzvv/closure FENV ([a][e] FBOD a e : freezevv _ ([x] BOD’ x a e) (ZBOD a e)))
%%
([x][wx:ccofvv x AFREE]
ccov_closure (ccov_pair (ccov_ch wx) (WENV’ x wx))
([a][wa : ccofv a AARG W]
[e][we : ccofv e ((csh AFREE) c& AENV) W]
cco_fst we
([exh][wexh:ccofv exh (csh AFREE) W]
cco_snd we
([envtail][wenvtail:ccofv envtail AENV W]
cco_lets wexh
([ex][wex:ccofvv ex AFREE]
WBOD’ ex wex a wa envtail wenvtail
359
) % lets body
(ZBOD a envtail) % frozen wrt lets
) % snd body
(f/lets vf/closed
[y] ZwrtENV’ y a) % frozen wrt snd proj
) % fst body
(f/snd vf/closed [envtail]
f/lets vf/var [ex]
f/closed
) %{ frozen wrt fst proj }% )
([v]
f/fst vf/var [exh]
f/snd vf/var [envtail]
f/lets vf/closed [ex]
f/closed)
([v]
f/fst vf/closed [exh]
f/snd vf/closed [envtail]
f/lets vf/closed [ex]
ZwrtARG’ ex envtail))
%%
<- vfreezevv/ok WENV FENV (WENV’ : {x}{ofx} ccofv (ENV’ x) AENV W)
<- ({a}{wa}{e}{we}
freezevv/ok ([x][wx] WBOD x wx a wa e we) (FBOD a e)
([x][wx] WBOD’ x wx a wa e we : ccof (BOD’ x a e) W))
<- ({z}
permafrostvv ([x] ZwrtARG x z) ([y] FBOD y z)
([x] ZwrtARG’ x z : frozen ([y] BOD’ x y z)))
<- ({y}
permafrostvv ([x] ZwrtENV x y) ([z] FBOD y z)
([x] ZwrtENV’ x y : frozen ([z] BOD’ x y z))).
- : vvfreezevv/ok D fzvv/closedvv D.
- : vvfreezevv/ok D fzvv/var D.
%block blockccvok : some {W:world} {A:ctyp} block {x: ccval} {xok: ccofv x A W}.
%block blockccvvok : some {Af:world -> ctyp} block {xx:ccvval}{xxok:ccofvv xx Af}.
360
- : freeze-gimme ([v] ccfst (V v) ([x] N v x)) (fz/fst ZN ZV)
<- vfreeze-gimme V ZV
<- ({x} freeze-gimme ([v] N v x) (ZN x)).
- : freeze-gimme ([v] ccsnd (V v) ([x] N v x)) (fz/snd ZN ZV)
<- vfreeze-gimme V ZV
<- ({x} freeze-gimme ([v] N v x) (ZN x)).
- : freeze-gimme ([v] ccwapp (V v) _ ([x] N v x)) (fz/wapp ZN ZV)
<- vfreeze-gimme V ZV
<- ({x} freeze-gimme ([v] N v x) (ZN x)).
- : freeze-gimme ([v] ccunpack (V v) ([w][x] N v w x)) (fz/unpack ZN ZV)
<- vfreeze-gimme V ZV
<- ({x}{w} freeze-gimme ([v] N v w x) (ZN w x)).
- : freeze-gimme ([v] ccleta (V v) ([x] N v x)) (fz/leta ZN ZV)
<- vfreeze-gimme V ZV
<- ({x} freeze-gimme ([v] N v x) (ZN x)).
- : freeze-gimme ([v] ccput (V v) ([x] N v x)) (fz/put ZN ZV)
<- vfreeze-gimme V ZV
<- ({x} freeze-gimme ([v] N v x) (ZN x)).
- : freeze-gimme ([v] cclets (V v) ([x] N v x)) (fz/lets ZN ZV)
<- vfreeze-gimme V ZV
<- ({x} freeze-gimme ([v] N v x) (ZN x)).
- : freeze-gimme ([v] cclocalhost ([x] N v x)) (fz/localhost ZN)
<- ({x} freeze-gimme ([v] N v x) (ZN x)).
361
<- vfreezevv-gimme V ZV
<- ({x}{w} freezevv-gimme ([v] N v w x) (ZN w x)).
- : freezevv-gimme ([v] ccleta (V v) ([x] N v x)) (fzvv/leta ZN ZV)
<- vfreezevv-gimme V ZV
<- ({x} freezevv-gimme ([v] N v x) (ZN x)).
- : freezevv-gimme ([v] ccput (V v) ([x] N v x)) (fzvv/put ZN ZV)
<- vfreezevv-gimme V ZV
<- ({x} freezevv-gimme ([v] N v x) (ZN x)).
- : freezevv-gimme ([v] cclets (V v) ([x] N v x)) (fzvv/lets ZN ZV)
<- vfreezevv-gimme V ZV
<- ({x} freezevv-gimme ([v] N v x) (ZN x)).
- : freezevv-gimme ([v] cclocalhost ([x] N v x)) (fzvv/localhost ZN)
<- ({x} freezevv-gimme ([v] N v x) (ZN x)).
- : ccvv (covv VF) (ccovv VF’) <- ({w} ccv (VF w) (VF’ w)).
- : ccv (cov_lam [x][wx] WM x wx) (ccov_closure ccov_unit
([x][xof][e][eof] WM’’ x xof)
([x] f/closed)
([x] FwrtBOD))
<- ({x}{wx}{x’}{wx’ : ccofv x’ A W}{thm : ccv wx wx’}
cc (WM x wx) (WM’ x’ wx’ : ccof (M’ x’) W))
<- freeze-gimme M’ (Z : freeze _ _ FwrtBOD)
<- freeze/ok WM’ Z WM’’.
362
- : ccv (cov_ch WVV) (ccov_ch WVV’)
<- ccvv WVV WVV’.
- : ccv (cov_valid WVV) (ccov_valid WVV’)
<- ccvv WVV WVV’.
- : ccv (cov_pack A WV) (ccov_pack A WV’)
<- ccv WV WV’.
- : ccv (cov_held WV) (ccov_held WV’)
<- ccv WV WV’.
- : ccv (cov_pair WV1 WV2) (ccov_pair WV1’ WV2’)
<- ccv WV1 WV1’
<- ccv WV2 WV2’.
- : ccv (cov_wlam WV) (ccov_wlam WV’)
<- ({w} ccv (WV w) (WV’ w)).
- : ccv cov_const ccov_const.
363
<- freezevv/ok WN’ Z WN’’.
- : cc co_halt cco_halt.
364
Bibliography
[1] Martı́n Abadi. Logic in access control. In LICS ’03: Proceedings of the 18th Annual
IEEE Symposium on Logic in Computer Science, page 228, Washington, DC, USA,
2003. IEEE Computer Society. ISBN 0-7695-1884-2. 7.2.1
[2] Martı́n Abadi, Michael Burrows, Butler Lampson, and Gordon Plotkin. A calcu-
lus for access control in distributed systems. ACM Transactions on Programming
Languages and Systems, 15(4):706–734, 1993. ISSN 0164-0925. 7.2.1
[3] Martı́n Abadi, Cédric Fournet, and Georges Gonthier. Secure communica-
tions processing for distributed languages. In IEEE Symposium on Security
and Privacy, pages 74–88, 1999. URL [Link]/article/
[Link]. 7.2.2
[4] Andreas Abel. A third-order representation of the λµ-Calculus. In S.J. Ambler,
R.L. Crole, and A. Momigliano, editors, Electronic Notes in Theoretical Computer
Science, volume 58. Elsevier, 2001. 2
[5] Andrew Appel. Compiling With Continuations. Cambridge University Press, Cam-
bridge, 1991. ISBN 0521416957. 4.6
[6] Andrew W. Appel and Marcelo J. R. Gonçalves. Hash-consing garbage collection.
Technical Report CS-TR-412-93, Princeton University, 1993. 5.5.4
[7] John Billings, Peter Sewell, Mark Shinwell, and Rok Strnisa. The implementation
of hashcaml, 2006. URL [Link]
7.1.2
[8] John Billings, Peter Sewell, Mark Shinwell, and Rok Strnisa. Type-safe distributed
programming for OCaml. In ML Workshop 2006, September 2006. URL http:
//[Link]/˜pes20/hashcaml/. 7.1.2
[9] Robert D. Blumofe and Philip A. Lisiecki. Adaptive and reliable parallel comput-
ing on networks of workstations. In USENIX 1997 Annual Technical Conference on
UNIX and Advanced Computing Systems, pages 133–147, Anaheim, California, 1997.
URL [Link] 2.1
[10] Tijn Borghuis and Loe M. G. Feijs. A constructive logic for services and informa-
tion flow in computer networks. The Computer Journal, 43(4):274–289, 2000. 7.1.1
[11] Bert Bos, Tantek Çelik, Ian Hickson, and Håkon Wium Lie. Cascading style sheets
level 2 revision 1 (css 2.1) specification. Technical Report CR-CSS21-20070719,
365
W3C, July 2007. URL [Link]
6.1
[12] Peter Buneman, Shamim Naqvi, Val Tannen, and Limsoon Wong. Principles of
programming with complex objects and collection types. In ICDT ’92: Selected pa-
pers of the fourth international conference on Database theory, pages 3–48, Amsterdam,
The Netherlands, 1995. Elsevier Science Publishers B. V. 7.2.3
[13] Luı́s Caires and Luca Cardelli. A spatial logic for concurrency (part I). In Theoret-
ical Aspects of Computer Software (TACS), pages 1–37. Springer-Verlag LNCS 2215,
October 2001. 7.1.1
[14] Luı́s Caires and Luca Cardelli. A spatial logic for concurrency (part II). In Proceed-
ings of the 13th International Conference on Concurrency Theory (CONCUR), pages
209–225, Brno, Czech Republic, August 2002. Springer-Verlag LNCS 2421. 7.1.1
[15] Luca Cardelli and Andrew D. Gordon. Anytime, anywhere. Modal logics for
mobile ambients. In Proceedings of the 27th Symposium on Principles of Programming
Languages (POPL), pages 365–377. ACM Press, 2000. 7.1.1
[16] B. Chang, K. Crary, M. DeLap, R. Harper, J. Liszka, T. Murphy VII, and F. Pfen-
ning. Trustless grid computing in ConCert. In M. Parashar, editor, Grid Computing
– Grid 2002 Third International Workshop, pages 112–125. Springer-Verlag, Novem-
ber 2002. URL [Link] 2.1
[17] Bor-Yuh Evan Chang, Kaustuv Chaudhuri, and Frank Pfenning. A judgmental
analysis of linear logic. Technical Report CMU-CS-03-131R, Carnegie Mellon,
April; revised December 2003. URL [Link]
[Link]. 3.5.1
[18] Philippe Charles, Christian Grothoff, Vijay Saraswat, Christopher Donawa, Allan
Kielstra, Kemal Ebcioglu, Christoph von Praun, and Vivek Sarkar. X10: an object-
oriented approach to non-uniform cluster computing. SIGPLAN Notices, 40(10):
519–538, 2005. ISSN 0362-1340. 7.2.1
[19] Stephen Chong, Jed Liu, Andrew C. Myers, Xin Qi, Krishnaprasad Vikram, Lan-
tian Zheng, and Xin Zheng. Secure web applications via automatic partitioning.
In 21st ACM Symposium on Operating Systems Principles (SOSP), October 2007. URL
[Link] 7.1.3
[20] Aske Simon Christensen, Anders Møller, and Michael I. Schwartzbach. Extending
java for high-level web service construction. ACM Transactions on Programming
Languages and Systems, 25(6):814–875, 2003. ISSN 0164-0925. 7.2.3
[21] Ezra Cooper, Sam Lindley, Philip Wadler, and Jeremy Yallop. Links: Web pro-
gramming without tiers. In 5th International Symposium on Formal Methods for
Components and Objects (FMCO). Springer-Verlag, November 2006. To appear. 4.5,
7.1.3, 7.2.3
[22] R. Cooper and C. Krumvieda. Distributed programming with asynchronous or-
dered channels in distributed ML. In Proceedings of the ACM SIGPLAN workshop
366
on ML and its Applications, June 1992. 7.1.2
[23] Karl Crary. Toward a foundational typed assembly language (expanded version).
Technical Report CMU-CS-02-196, Carnegie Mellon University, 2002. 7.2.2
[24] Karl Crary. Linear logic. The Twelf Wiki, 2007. URL [Link]
org/wiki/Linear_logic. 4.7.1
[25] Karl Crary and Susmit Sarkar. Foundational certified code in a metalogical frame-
work. ACM Transactions on Computational Logic, 2007. To appear. 7.2.2
[26] Curry. The functional logic language Curry, 2007. URL: [Link]
[Link]/˜curry. 7.2.3
[27] L. Damas and R. Milner. Principal type schemes for functional programs. In
Principles of Programming Languages (POPL ’82), pages 207–212, 1982. 5.3.3
[28] R. Davies. A temporal-logic approach to binding-time analysis. In LICS ’96: Pro-
ceedings of the 11th Annual IEEE Symposium on Logic in Computer Science, page 184,
Washington, DC, USA, 1996. IEEE Computer Society. ISBN 0-8186-7463-6. 7.2.1
[29] Rowan Davies and Frank Pfenning. A modal analysis of staged computation. J.
ACM, 48(3):555–604, 2001. ISSN 0004-5411. 7.2.1
[30] Maarten de Rijke and Heinrich Wansing. Proofs and expressiveness in alethic
modal logic. In A Companion to Philosophical Logic, pages 422–441. 3.1.2
[31] Derek Dreyer. Understanding and Evolving the ML Module System. PhD thesis, Car-
negie Mellon, May 2005. Available as CMU Technical Report CMU–CS–05–131.
7.2.2
[32] ECMA. ECMAScript language specification. Technical Report ECMA-262,
ECMA, December 1999. 5.1.1, 5.4.9
[33] Sameh El-Ansary, Donatien Grolaux, Peter Van Roy, and Mahmoud Rafea. Over-
coming the multiplicity of languages and technologies for web-based devel-
opment using a multi-paradigm approach. In Multiparadigm Programming in
Mozart/Oz, Second International Conference (MOZ 2004), Revised Selected and Invited
Papers, pages 113–124, October 2004. 7.1.3
[34] M. Elsman and K. F. Larsen. Typing XHTML web applications in ML. In Practical
Aspects of Declarative Languages PADL, pages 224–238, 2004. 7.2.3
[35] E. Allen Emerson. Temporal and modal logic, pages 995–1072. MIT Press, Cam-
bridge, MA, USA, 1990. ISBN 0-444-88074-7. 7.2.1
[36] A. P. Ershov. On programming of arithmetic operations. Communications of the
ACM, 1(8):3–6, 1958. ISSN 0001-0782. 5.5.4
[37] Jim Farley. Java Distributed Computing. O’Reilly, January 1998. ISBN 1595922069.
2.2
[38] R. Fielding, J. Gettys, J. Mogul, H. Frystyk, L. Masinter, P. Leach, and T. Berners-
Lee. Hypertext Transfer Protocol – HTTP/1.1. RFC 2616 (Draft Standard), June
1999. URL [Link] Updated by RFC
367
2817. 5.5.1
[39] R. Fielding, J. Gettys, J. Mogul, H. Frystyk, L. Masinter, P. Leach, and T. Berners-
Lee. Hypertext Transfer Protocol – HTTP/1.1. RFC 2817 (Draft Standard), June
1999. URL [Link] 5.5.1
[40] Cédric Fournet and Georges Gonthier. The reflexive CHAM and the Join-calculus.
In Proceedings of the 23rd ACM Symposium on Principles of Programming Languages,
pages 372–385. ACM Press, 1996. URL [Link]
[Link]. 7.1.2
[41] N. Freed and N. Borenstein. Multipurpose internet mail extensions (MIME) part
one: Format of internet message bodies. RFC 2045 (Draft Standard), November
1996. URL [Link] 5.5.4
[42] Richard Frost and John Launchbury. Constructing natural language interpreters
in a lazy functional language. The Computer Journal, Special edition on Lazy Func-
tional Programming, 32:108–121, 1989. 5.3.1
[43] Philippa Gardner, Gareth Smith, Mark Wheelhouse, and Uri Zarfaty. DOM: To-
wards a formal specification. In PLAN-X 2008: Programming Language Techniques
for XML, January 2008. URL [Link]
PLANX2008/papers/[Link]. 7.2.3
[44] Deepak Garg and Frank Pfenning. Non-interference in constructive authorization
logic. In Proceedings of the 19th Computer Security Foundations Workshop (CSFW’06),
pages 183–293, July 2006. 7.2.1
[45] Jesse James Garrett. Ajax: A new approach to web applications, Febru-
ary 2005. URL [Link]
[Link]. 7.2.3
[46] J.-Y. Girard. Linear logic. Theoretical Computer Science, pages 1–102, 1987. 4.7.1
[47] Timothy G. Griffin. The formulae-as-types notion of control. In
Conf. Record 17th Annual ACM Symp. on Principles of Programming Lan-
guages, POPL’90, San Francisco, CA, USA, 17–19 Jan 1990, pages 47–57.
ACM Press, New York, 1990. URL [Link]
[Link]. 3.4.1
[48] Michael Hanus. Type-oriented construction of web user interfaces. In PPDP ’06:
Proceedings of the 8th ACM SIGPLAN symposium on Principles and practice of declar-
ative programming, pages 27–38, New York, NY, USA, 2006. ACM. ISBN 1-59593-
388-3. 7.2.3
[49] Robert Harper. Practical Foundations for Programming Languages. Working draft,
2007. URL [Link] 5.4.1
[50] Robert Harper and Karl Crary. How to believe a Twelf proof, 2006. URL http:
//[Link]/˜rwh/papers/how/[Link]. 4.4.1
[51] Robert Harper, Furio Honsell, and Gordon Plotkin. A framework for defining
logics. Journal of the Association for Computing Machinery, 40(1):143–184, January
368
1993. 4.4.1
[52] Robert Harper and Daniel R. Licata. Mechanizing metatheory in a logical frame-
work. Journal of Functional Programming, 2007. URL [Link]
˜rwh/papers/mech/[Link]. To appear. 4.4.1, 4.4.1, 4.4.1
[53] Robert Harper and Greg Morrisett. Compiling polymorphism using intensional
type analysis. In Proceedings of the 22nd Symposium on Principles of Programming
Languages (POPL), pages 130–141, San Francisco, California, January 1995. 7.1.2
[54] Robert Harper and Benjamin C. Pierce. Design considerations for ML-style mod-
ule systems. In Benjamin C. Pierce, editor, Advanced Topics in Types and Program-
ming Languages, chapter 8, pages 293–346. MIT Press, 2005. 5.1.3
[55] Robert Harper and Chris Stone. A type-theoretic interpretation of Standard ML.
In Gordon Plotkin, Colin Stirling, and Mads Tofte, editors, Proof, Language, and
Interaction: Essays in Honor of Robin Milner. MIT Press, 2000. 7.2.2
[56] Matthew Hennessy, Julian Rathke, and Nobuko Yoshida. SafeDPi: A language
for controlling mobile code. Report 02/2003, Department of Computer Science,
University of Sussex, October 2003. 7.1
[57] Michael Huth and Mark Ryan. Logic in computer science. Cambridge Univer-
sity Press, 2004. ISBN 0-521-54310X. URL [Link]
research/projects/lics/. 7.2.1
[58] Graham Hutton. Higher-order functions for parsing. Journal of Functional Pro-
gramming, 2:323–343, 1992. 5.3.1
[59] [Link]. Hybrid logics bibliography, 2005. URL: [Link]
content/[Link]. 3.1
[60] W3C DOM IG. Document object model, January 2005. URL: [Link]
DOM/. 5.1.3
[61] Collin Jackson, Andrew Bortz, Dan Boneh, and John C. Mitchell. Protecting
browser state from web privacy attacks. In Proceedings of the 15th ACM World
Wide Web Conference (WWW 2006), 2006. URL [Link]
edu/sameorigin/[Link]. 5.5.2
[62] Limin Jia and David Walker. Modal proofs as distributed programs (extended
abstract). European Symposium on Programming, 2004. URL [Link]
[Link]/sip/pub/[Link]. 3.1, 3.2.1, 3.5.4, 7.1.1
[63] JoCaml. JoCaml web site, 2005. URL: [Link]
7.1.2
[64] Clifford Dale Krumvieda. Distributed ML: abstractions for efficient and fault-tolerant
programming. PhD thesis, Cornell University, Department of Computer Science,
Ithaca, N.Y., August 1993. URL [Link] COR
93-1376. 7.1.2
[65] Avijit Kumar and Robert Harper. A language for access control. Technical Re-
port CMU–CS–07–140, Carnegie Mellon University School of Computer Science,
369
Pittsburgh, PA, July 2007. 7.2.1
[66] Daniel K. Lee, Karl Crary, and Robert Harper. Towards a mechanized metathe-
ory of Standard ML. In POPL ’07: Proceedings of the 34th annual ACM SIGPLAN–
SIGACT symposium on Principles of programming languages, pages 173–184, New
York, NY, USA, January 2007. ACM Press. ISBN 1-59593-575-4. URL http:
//[Link]/˜dklee/papers/[Link]. 4.7.1
[67] Daniel K. Lee, Karl Crary, and Robert Harper. Towards a mechanized metatheory
of Standard ML. Technical Report CMU-CS-06-138, Carnegie Mellon, January
2007. URL [Link] 4.7.1
[68] Daniel K. Lee and et al. Effectiveness lemma. The Twelf Wiki, 2007. URL http:
//[Link]/wiki/Effectiveness_lemma. 4.4.2
[69] Håkon Wium Lie and Bert Bos. Cascading style sheets, level 1. Technical Report
REC-CSS1-19990111, W3C, Jan 1999. URL [Link]
REC-CSS1-19990111. 6.1
[70] V. Wiktor Marek, Grigori Schwarz, and Miroslaw Truszczynski. Ranges of strong
modal nonmonotonic logics. In Proceedings of the 1st International Workshop on Non-
monotonic and Inductive Logic, pages 85–99, London, UK, 1991. Springer-Verlag.
ISBN 3-540-54564-6. 3.1.2
[71] MediaWiki. Mediawiki, 2007. URL: [Link] 6.3
[72] Sun Microsystems. Java object serialization specification, 2005. URL
[Link]
spec/[Link]. 2.2
[73] R. Milner. A Calculus of Communicating Systems. Springer-Verlag New York, Inc.,
Secaucus, NJ, USA, 1982. ISBN 0387102353. 7.1.2
[74] R. Milner, J. Parrow, and D. Walker. A calculus of mobile processes, parts I and II.
Information and Computation, pages 1–40, 41–71, September 1992. 7.1
[75] Robin Milner. A theory of type polymorphism in programming. Journal of Com-
puter and System Sciences, 17:348–375, 1978. 5.3.3
[76] Robin Milner. Communication and Concurrency. Prentice Hall International (UK)
Ltd., Hertfordshire, UK, UK, 1995. ISBN 0-13-115007-3. 7.1
[77] Robin Milner, Mads Tofte, Robert Harper, and David MacQueen. The Definition of
Standard ML (Revised). MIT Press, Cambridge, Massachusetts, 1997. ISBN 0-262-
63181-4. 3.5, 5.1
[78] Y. Minamide, G. Morrisett, and R. Harper. Typed closure conversion. In Proceed-
ings of the 23th Symposium on Principles of Programming Languages (POPL), pages
271–283, 1996. 4.7
[79] MLton. MLton: Unresolved bugs, 2007. URL: [Link]
UnresolvedBugs. 5.3.1
[80] Jonathan Moody. Modal logic as a basis for distributed computation. Technical
370
Report CMU-CS-03-194, Carnegie Mellon University, October 2003. 7.1.1
[81] Jonathan Moody. Logical mobility and locality types. In Sandro Etalle,
editor, Logic Based Program Synthesis and Transformation (LOPSTR), LNCS.
Springer, 2005. URL [Link]
[Link]. 7.1.1
[82] Greg Morrisett, Karl Crary, Neal Glew, Dan Grossman, Richard Samuels, Freder-
ick Smith, David Walker, Stephanie Weirich, and Steve Zdancewic. TALx86: A
realistic typed assembly language. In 1999 ACM SIGPLAN Workshop on Compiler
Support for System Software, pages 25–35, Atlanta, Georgia, May 1999. 7.2.2
[83] Greg Morrisett, David Walker, Karl Crary, and Neal Glew. From System F to typed
assembly language. ACM Transactions on Programming Languages and Systems, 21
(3):527–568, May 1999. 4.7
[84] Tom Murphy, VII. The wizard of TILT: Efficient? , convenient and abstract
type representations. Technical Report CMU-CS-02-120, Carnegie Mellon School
of Computer Science, 2002. URL [Link]
edu/anon/2002/abstracts/[Link]. 5.1.4, 5.4.1
[85] Tom Murphy, VII. Grid ML programming with ConCert. In ML Workshop 2006,
September 2006. URL [Link] 2.1, 5.2.1, 5.5.1, 5.5.4, 7.2.2
[86] Tom Murphy, VII. Modal types for mobile code (thesis proposal). Technical Re-
port CMU-CS-06-112, Carnegie Mellon, Pittsburgh, Pennsylvania, USA, Feb 2006.
URL [Link] 3
[87] Tom Murphy, VII, Karl Crary, and Robert Harper. Distributed control flow with
classical modal logic. In Luke Ong, editor, 14th Annual Conference of the European
Association for Computer Science Logic (CSL 2005), Lecture Notes in Computer Sci-
ence. Springer, August 2005. URL [Link]
[88] Tom Murphy, VII, Karl Crary, and Robert Harper. Type-safe distributed program-
ming with ML5. In Trustworthy Global Computing 2007, November 2007. URL
[Link] 5
[89] Tom Murphy, VII, Karl Crary, Robert Harper, and Frank Pfenning. A symmetric
modal lambda calculus for distributed computing. In Proceedings of the 19th IEEE
Symposium on Logic in Computer Science (LICS 2004). IEEE Press, July 2004. URL
[Link] 3.3
[90] Tom Murphy, VII, Karl Crary, Robert Harper, and Frank Pfenning. A symmet-
ric modal lambda calculus for distributed computing (extended technical report).
Technical Report CMU-CS-04-105, Carnegie Mellon University, Mar 2004. URL
[Link] 3.2.3
[91] Tom Murphy, VII, Daniel Spoonhower, Chris Casinghino, Daniel R. Licata, Karl
Crary, and Robert Harper. The cult of the bound variable: The 9th annual ICFP
programming contest. Technical Report CMU-CS-06-163, Carnegie Mellon, Octo-
ber 2006. URL [Link]
371
abstracts/[Link]. 5.4.1
[92] Chetan Murthy. Classical proofs as programs: How, what and why. Technical
Report TR91-1215, Cornell University, 1991. 3.4.1
[93] Andrew C. Myers, Lantian Zheng, Steve Zdancewic, Stephen Chong, and
Nathaniel Nystrom. Jif: Java information flow, July 2001. Software release, URL:
[Link] 7.1.3
[94] Aleksandar Nanevski, 2006. Personal communication and locally circulated doc-
ument. 3.4.2
[95] George C. Necula. Proof-carrying code. In Proceedings of the 24th ACM SIGPLAN-
SIGACT Symposium on Principles of Programming Langauges (POPL ’97), pages
106–119, Paris, January 1997. URL [Link]
html. 7.2.2
[96] O’Caml. O’caml online documentation, 2005. URL: [Link]
7.1.2
[97] Atsushi Ohori and Kazuhiko Kato. Semantics for communication primitives in
an polymorphic language. In Conference Record of the Twentieth Annual ACM
SIGPLAN-SIGACT Symposium on Principles of Programming Languages, pages 99–
112, Charleston, South Carolina, 1993. URL [Link]
edu/[Link]. 7.1.2
[98] Chris Okasaki. Purely Functional Data Structures. Cambridge Univer-
sity Press, Cambridge, UK, 1998. URL [Link]
[Link]. 5.1.4
[99] Michel Parigot. λµ-Calculus: An algorithmic interpretation of classical natural
deduction. In Andrei Voronkov, editor, Logic Programming and Automated Rea-
soning, International Conference LPAR’92, St. Petersburg, Russia, July 15–20, 1992,
Proceedings, volume 624 of Lecture Notes in Computer Science. Springer, 1992. ISBN
3-540-55727-X. 3.4.1
[100] Sungwoo Park. A modal language for the safety of mobile values. Technical
Report CMU-CS-05-124, Carnegie Mellon, Pittsburgh, Pennsylvania, USA, May
2005. 3.5.4, 7.1.1
[101] Sungwoo Park. A modal language for the safety of mobile values. In Fourth
ASIAN Symposium on Programming Languages and Systems, November 2006. 3.5.4,
7.1.1
[102] Sungwoo Park. Type-safe higher-order channels in ML-like languages. In In-
ternational Conference on Functional Programming (ICFP) 2007, October 2007. URL
[Link] 7.1.1
[103] Simon Peyton Jones, editor. Haskell 98 Language and Libraries: The Revised Report.
Cambridge University Press, April 2003. ISBN 9780521826143. 7.2.3
[104] Frank Pfenning. Structural cut elimination: I. Intuitionistic and classical logic.
Information and Computation, 157(1-2):84–141, 2000. URL [Link]
372
[Link]/[Link]. 3.2.3
[105] Frank Pfenning. Logical frameworks. In Alan Robinson and Andrei Voronkov,
editors, Handbook of Automated Reasoning, chapter 17, pages 1063–1147. Elsevier
Science and MIT Press, 2001. URL [Link]
[Link]. 4.4.1
[106] Frank Pfenning. Automated Theorem Proving. Carnegie Mellon University, 2004.
URL [Link] Draft. 3.4.3, 4.7
[107] Frank Pfenning and Rowan Davies. A judgmental reconstruction of modal logic.
Mathematical Structures in Computer Science, 11:511–540, July 2001. Notes to an in-
vited talk at the Workshop on Intuitionistic Modal Logics and Applications (IMLA’99).
7.1.1
[108] Frank Pfenning and Conal Elliott. Higher-order abstract syntax. In Proceedings
of the ACM SIGPLAN’88 Conference on Programming Language Design and Imple-
mentation (PLDI), pages 199–208, 1988. URL [Link]
papers/[Link]. 4.7.1
[109] Benjamin C. Pierce and David N. Turner. Pict: a programming language based on
the pi-calculus. In Gordon Plotkin, Colin Stirling, and Mads Tofte, editors, Proof,
language, and interaction: essays in honour of Robin Milner, pages 455–494. MIT Press,
Cambridge, MA, USA, 2000. ISBN 0-262-16188-5. 7.1
[110] A. M. Pitts and M. J. Gabbay. A metalanguage for programming with bound
names modulo renaming. In R. Backhouse and J. N. Oliveira, editors, Mathematics
of Programme Construction. 5th International Conference (MPC2000), volume 1837 of
Lecture Notes in Computer Science, pages 230–255. Springer-Verlag, July 2000. 5.4.1
[111] David Plainfossé and Marc Shapiro. A survey of distributed collection techniques.
Technical report, BROADCAST, 1994. 7.2.2
[112] Gordon Plotkin. Call-by-name, call-by-value, and the lambda calculus. Theoretical
Computer Science, 1:125–159, 1975. URL [Link]
gdp/publications/cbn_cbv_lambda.pdf. 4.6
[113] Twelf Project. %total. The Twelf Wiki, 2007. URL [Link]
wiki/%25total. A.3.1
[114] A. S. Rao and M. P. Georgeff. BDI-agents: from theory to practice. In Proceedings
of the First Intl. Conference on Multiagent Systems, San Francisco, 1995. URL http:
//[Link]/[Link]. 7.2.1
[115] John Reppy. Concurrent Programming in ML. Cambridge University Press, 1999.
7.1.2
[116] Greg Restall. Proofnets for S5: sequents and circuits for modal logic. Logic Collo-
quium 2005, (28), 2007. 3.1.2
[117] Andreas Rossberg. Typed Open Programming - A higher-order, typed approach to dy-
namic modularity and distribution. PhD thesis, Universität des Saarlandes, January
2007. URL [Link]
373
html. (preliminary version). 7.1.2
[118] Andreas Rossberg, Didier Le Botlan, Guido Tack, Thorsten Brunklaus, and Gert
Smolka. Alice through the looking glass. In Hans-Wolfgang Loidl, editor, Trends
in Functional Programming, Volume 5, volume 5 of Trends in Functional Program-
ming. Intellect, Munich, Germany, 2004. URL [Link]
˜rossberg/[Link]. 7.1.2
[119] Andreas Rossberg, Guido Tack, and Leif Kornstaedt. HOT pickles, and how to
serve them. In ACM-SIGPLAN Workshop on ML, October 2007. URL http://
[Link]/˜rossberg/[Link]. 7.1.2
[120] Jesse Ruderman. The same origin policy, 2001. URL: [Link]
org/projects/security/components/[Link]. 5.5.2, 7.2.1
[121] Carsten Schürmann and Frank Pfenning. A coverage checking algorithm for LF.
In D. Basin and B. Wolff, editors, Proceedings of the 16th International Conference on
Theorem Proving in Higher Order Logics (TPHOLs 2003), pages 120–135, Rome, Italy,
September 2003. Springer-Verlag LNCS 2758. URL [Link]
˜fp/papers/[Link]. 3.3, 4.4.1
[122] M. Serrano, E. Gallesio, and F. Loitsch. HOP, a language for programming the
Web 2.0. In Proceedings of the First Dynamic Languages Symposium, October 2006.
URL [Link] 7.1.3, 7.2.3
[123] Peter Sewell, James J. Leifer, Keith Wansbrough, Francesco Zappa Nardelli, Mair
Allen-Williams, Pierre Habouzit, and Viktor Vafeiadis. Acute: high-level pro-
gramming language design for distributed computation. In International Confer-
ence on Functional Programming (ICFP) 2005, Tallinn, Estonia, September 2005. 7.1.2
[124] Peter Sewell, James J. Leifer, Keith Wansbrough, Francesco Zappa Nardelli, Mair
Allen-Williams, Pierre Habouzit, and Viktor Vafeiadis. Acute: high-level pro-
gramming language design for distributed computation. Journal of Functional Pro-
gramming, 17(4-5):547–612, 2007. ISSN 0956-7968. 7.1.2
[125] Peter Sewell, Pawel Wojciechowski, and Benjamin Pierce. Location-independent
communication for mobile agents: a two-level architecture. Technical Report 462,
Computer Laboratory, University of Cambridge, April 1999. 7.1
[126] Alex K. Simpson. The Proof Theory and Semantics of Intuitionistic Modal Logic. PhD
thesis, University of Edinburgh, 1994. 3.1, 3.2
[127] G. Smolka. The Oz programming model. Computer Science Today, 1000:324–343,
1995. 7.1.3
[128] P. Srisuresh and M. Holdrege. IP network address translator (NAT) terminol-
ogy and considerations, August 1999. URL [Link]
rfc2663. 7.2.1
[129] David Swasey, Tom Murphy, VII, Karl Crary, and Robert Harper. A separate com-
pilation extension to Standard ML. In ML Workshop 2006, September 2006. URL
[Link] 5.1, 5.1.3
374
[130] David Swasey, Tom Murphy, VII, Karl Crary, and Robert Harper. A
separate compilation extension to Standard ML (revised and expanded).
Technical Report CMU-CS-06-104R, Carnegie Mellon University, January
2006. URL [Link]
abstracts/[Link]. 5.1, 5.1.3, 5.3.3
[131] Peter Thiemann. An embedded domain-specific language for type-safe server-
side web scripting. ACM Trans. Inter. Tech., 5(1):1–46, 2005. ISSN 1533-5399. 7.2.3
[132] Bent Thomsen, Lone Leth, and Tsung-Min Kuo. A Facile tutorial. In CONCUR ’96:
Proceedings of the 7th International Conference on Concurrency Theory, pages 278–298,
London, UK, 1996. Springer-Verlag. ISBN 3-540-61604-7. 7.1.2
[133] Bent Thomsen, Lone Leth, Sanjiva Prasad, Tsung-Min Kuo, Andre Kramer, Fritz
Knabe, and Alessandro Giacalone. Facile Antigua release programming guide.
Technical Report ECRC-93-20, European Computer-Industry Research Centre,
Munich, Germany, December 1993. 7.1.2
[134] TILT. The TILT compiler, 2004–2005. URL: [Link]
5.3.3
[135] Asis Unypoth and Peter Sewell. Nomadic Pict: Correct communication infras-
tructure for mobile computation. In Proceedings of the 28th Annual ACM SIGPLAN-
SIGACT symposium on Principles of Programming Languages (POPL), London, UK,
January 2001. 7.1
[136] C. Joseph Vanderwaart. Static Enforcement of Timing Policies Using Code Certifi-
cation. PhD thesis, Carnegie Mellon University, August 2006. URL http://
[Link]/anon/2006/[Link].
Available as CMU Technical Report CMU–CS–06–143. 5.5.3
[137] Joseph C. Vanderwaart, Derek R. Dreyer, Leaf Petersen, Karl Crary, Robert
Harper, and Perry Cheng. Typed compilation of recursive datatypes. In TLDI
’03: 2003 ACM SIGPLAN International Workshop on Types in Language Design and
Implementation, pages 98–108, January 2003. URL [Link]
˜rwh/papers/datatypes/[Link]. 5.1.4, 3
[138] Steve Vinoski. CORBA: integrating diverse applications within distributed het-
erogeneous environments. IEEE Communications Magazine, 14(2), 1997. URL
[Link] 2.2
[139] Philip Wadler. Views: A way for pattern matching to cohabit with data abstrac-
tion. In Steve Munchnik, editor, Proceedings, 14th Symposium on Principles of Pro-
gramming Languages (POPL), pages 307–312. Association for Computing Machin-
ery, 1987. 5.1.4
[140] Philip Wadler. Call-by-value is dual to call-by-name. In Proceedings of the 8th
International Conference on Functional Programming (ICFP). ACM Press, August
2003. URL [Link]
html. 3.4.2
375
[141] Philip Wadler. Call-by-value is dual to call-by-name, reloaded. In Rewriting Tech-
niques and Applications, April 2005. URL [Link]
wadler/topics/[Link]. 3.4.2
[142] Ken Wakita, Takashi Asano, and Masataka Sassa. D’Caml: A native dis-
tributed ML compiler for heterogeneous environment. In Patrick Amestoy,
Philippe Berger, Michel J. Daydé, Iain S. Duff, Valérie Frayssé, Luc Giraud,
and Daniel Ruiz, editors, Euro-Par 1999 Parallel Processing, 5th International Euro-
Par Conference, volume 1685 of Lecture Notes in Computer Science, pages 914–924.
Springer, 1999. ISBN 3-540-66443-2. URL [Link]
[Link]. 7.1.2
[143] Wikipedia. Wikipedia, the free encyclopedia, 2007. URL: [Link]
[Link]/. 6.3
[144] Limsoon Wong. Kleisli, a functional query system. J. Funct. Program., 10(1):19–56,
2000. ISSN 0956-7968. 7.2.3
[145] Zhonghua Yang and Keith Duddy. CORBA: A platform for distributed object
computing (a state-of-the-art report on OMG/CORBA). Operating Systems Review,
30(2):4–31, 1996. URL [Link]
html. 2.2
376