The Zombie typing rules, and the smaller calculus in the previous subsection, were inspired by constructive versions of modal logic, which reason about statements whose truth varies in different “possible worlds”.16 In our case the two possible worlds are L and P. One could consider Zombie as two separate type systems, which happen to share the same program syntax, have mostly the same typing rules, and provide features for interoperability.
However, there is another way to think about nontermination. In that conception, there is a single language, and divergence is considered as an effect that can happen as a program executes, similar to raising an exception or printing output. This view is implicit in the long line of work, e.g. by Capretta [28], which treats nontermination in dependently typed languages as a monad.
If we think of nontermination as an effect, then the most natural way to formalize it is as atype-and-effect system [57]. By now the programming language community has arrived at a fairly standardized way to formulate effect systems for functional languages, parameterized over some abstract lattice of effects. In this section of the chapter, we instantiate this general approach in a small dependently typed calculus which tracks nontermination.
16Modal logic has previously been used to design type systems for distributed computation [69, 94].
In particular Zombie was inspired by ML5 [94], in which the typing judgement is indexed by what “world” (computer in a distributed system) a program is running on, and which includes a type A@θinternalizing that judgement.
T ::=Nat|(x:T)→θ T0 |Σx:T.T0 |t =t0
t ::=x |t t0 |λx.t | ht,t0i |pcasez t of {(x, y)⇒t0} | 0|Suct |join|recf x.t |indf x.t
| ncasez t of {Z ⇒t1;Sx ⇒t2}
u ::=x | hu1,u2i |0|Sucu
| λx.a |recf x.a |indf x.a |join| hv1,v2i |0|Sucv
Figure 7.9: Effect-style calculus: Types, expressions, and values
The syntax of the calculus is shown in Figure 7.9. To keep things simple, we use separate syntactic categories for terms and types. Types include natural numbers, dependent functions (x:T)→θ T0, dependent pairs Σx:T.T0, and equations between terms. The difference compared to the possible-world-style calculus is that we no longer have @-types; instead each function type is tagged with a θ which is P if calling the function may cause nontermination.
The terms t in the system are exactly the same as the terms in the possible-world calculus, and their operational semantics are also the same. The types we assign them will be different, but since we are formalizing the erased version of the type system terms do not contain type annotations.
The typing rules are shown in Figures 7.10 and 7.11. To keep the two systems visually distinct, we write the effect-style typing judgement Γ `θ t : T, with the θ as a subscript instead of a superscript, and the names of the effect-style typing rules include underscores.
The general principle is that the θ in the effect-style system tracks only effects from evaluating the particular expression in question, whereas the possible-world style system it also tracks what would happen if a client were to call that expression (when it is a function). We do not include an explicit subsumption rule in the type system, but the other rules are formulated so that this is admissible:
Lemma 29 (Subsumption). If Γ`L t :T then Γ`P t :T.
All the important differences between the possible-worlds and the effects calculus can be seen in the typing rules for variables, functions, and applications.
Variables only range over values, so referencing a variable in the context can never cause nontermination. So T Var checks at any effect θ. This also means that there
is no need to record an effect with variables in the context, so in this system contexts are just lists of types of variables:
Γ`T Γ`T Γ,x :T `T0 Γ`(x:T)→θ T0k Arr Γ`T Γ,x :T `T0 Γ`Σx:T.T0 k Sigma `Γ Γ`Natk Nat Γ`θ t :T Γ`θ0 t0 :T0 Γ`t =t0 k Eq Γ`θ t :T (x :T)∈Γ `Γ Γ`T Γ`θ x :T T Var Γ`θ t : (x:T0) θ0 →T Γ`θ t0 :T0 θ0 ≤θ Γ` {t0/x}T Γ`θ t t0 :{t0/x}T T App Γ,x :T0 `θ0 t :T Γ`(x:T0) θ 0 →T Γ`θ λx.t : (x:T0) θ0 →T T Lam Γ,f : (x:T0)→P T,x :T0 `P t :T Γ`(x:T0)→P T Γ`θ recf x.t : (x:T0) P →T T Rec
Γ,x :Nat,f : (y:Nat)→L (p:x =Sucy)→L T `Lt :T
Γ`(x:Nat)→L T Γ`θ indf x.t : (x:Nat) L →T T Ind `Γ Γ`θ 0 :Nat T Zero Γ`θ t :Nat Γ`θ Suct :Nat T Suc Γ`θ t :Nat Γ,z : 0 =t `θ t1 :T Γ`T Γ,x :Nat,z : (Sucx) = t `θ t2 :T Γ`θ ncasez t of {Z ⇒t1;Sx ⇒t2}:T T NCase Γ`Σx:T1.T2 Γ`θ t1 :T1 Γ`θ t2 :{t1/x}T2 Γ` {t1/x}T2 Γ`θ ht1,t2i: Σx:T1.T2 T Pair Γ`θ t : Σx:T1.T2 Γ,x :T1,y :T2,z :hx,yi=t `θ t0 :T Γ`T Γ`θ pcasez t of {(x, y)⇒t0}:T T PCase t ;∗p t0 t0 ;∗p t0 Γ`P t :T Γ`P t0 :T0 Γ`θ join:t =t0 T Join Γ`θ t :{t1/x}T Γ`Lt0 :t1 =t2 Γ` {t2/x}T Γ`θ t :{t2/x}T T Conv
Similar reasoning holds for function definitions. A lambda-expression λx.t is already a value, so evaluating it can never diverge and the ruleT Lamcan be checked at any
θ. However, the body of the function may diverge when the function is called (it has effect θ0, which may be P), and we record this information by the θ0 in the function type. Similar reasoning holds for generally recursive functions (T Rec), where the
function definition itself is terminating but the P in the function type (x:T0) →P T
records that it is dangerous to call. Structually recursive functions (T Ind) are given
the type (x:Nat)→L T, with an L.
Function types are eliminated by the application rule TApp, which is the classic
type-and-effect style application rule. It combines three sources of nontermination: the final effect has to be P if the evaluation of t or t0 may diverge (θ), or if t may diverge when called (θ0).
All the other rules in the system follow the corresponding rules in the possible-world- style calculus quite closely, with the only difference being that variables in the context are not tagged with a θ.
7.4.1
Mixing
L
and
P
expressions in a program
The possible-world system used @-types (the Box/Unbox rules) and mobile types
(the TMobileVal rule) to mix expressions with different θs. In the effect-style
calculus we have neither of these features, so the same use-cases are expressed slightly differently.
For example, we saw in Section 7.3.3 how a possibly nonterminating functionf could require its argument to be a proof, by the type f : A@L → B. In the effect-style system the meaning of a type is always completely specified by the classifiers on the arrows inside it, so we instead express f’s requirements in the type A itself. For example, suppose f wants a logical proof that some variable x is nonzero:
Γ`P f : ((x = 0 →False)@L)→B
In the effect-style system we write this by making f expect a terminating function: Γ`P f : (x = 0
L
→False)→P T
Conversely, we saw that a logical function g can be applied to a value in P, if it has the type g : A@P → B. In the effect style system this use-case is also supported, because if a value is typeable at all, it is typeable at L:
So if a functiont can be checked atL, and the function is logical (i.e. the latent effect int’s type is L), then the application to any value t u can be checked at L also. We also saw how Zombie allows generally recursive functions to return proofs, e.g. the functionsolver in Section 7.2.2. These examples also fit well into the effect-style system. We could give the SAT-solver a type like the following:
solver : (f : Formula) →P Either (Σ(v:Assignment). eval f v = true) ((v:Assignment) →L eval f v = false) Here the termination behaviour can be read off the classifiers on the arrows: a call to solve might not terminate (→P), but if it does terminate, then the function it returns is total (→L).
Finally, theTMobileValrule in the possible-world-style system expresses the payoff of CBV evaluation: no matter which fragment it came from, once you have evaluated it a number is a number (and an equality proof is an equality proof, etc). This part of the story works even better in the effect language. If we have
foo : (x : Nat) →P (2 = 2) bar : (2 = 2) →L Nat
we are allowed to directly form the applicationbar (foo 3)(which will be given the effect P). On the other hand, if foo was in P and we wanted to use the rule TMo- bileVal this would have to be explicitly sequenced as let x = foo 3 in bar x in order to create a syntactic value x.
As illustrated by the above example, we do not need to include an explicit rule for mobile values in the effect system. In some sense this is becauseevery type is mobile: the meaning of function types is determined by the annotation on the arrow, rather than by theθon the judgement, so types in the effect system always classify the same values in L as in P contexts.
This property seems promising if we consider (in future work) extending the system to include polymorphism. With just the typing rules presented in this section, the effect style system is a little more heavy on annotations than the possible-world-style system: here every function needs to be marked with a θ, whereas in the possible- world system, only functions with a non-mobile argument (i.e. higher-order functions) need an @θ annotation. But in the effect system every type is mobile, so we would not need to add annotations to type variables, and that should make types considerably less cluttered.
T <:T0 θ≤θ0 T10 <:T1 T2 <:T20 (x:T1) θ →T2 <: (x:T10) θ0 →T20 Sub Arr T2 <:T20 Σx:T1.T2 <: Σx:T1.T20 Sub Sigma
Nat<:NatSub Nat (t1 =t2)<: (t1 =t2) Sub Eq Γ`θ t :T Γ` θ t :T T <:T0 Γ`T0 Γ`θ t :T0 T Subtype
Figure 7.11: Effect-style typing: subtyping
7.4.2
Subtyping
Just like in the possible-world style system, we may wish to go further than just subsumption. A function (x:T)→L T0 is strictly better than (x:T)→P T0, even when it appears inside a larger type. In order to reflect this, we add a subtyping relation to the language (Figure 7.11). The rule Sub Arrstates that a function type is better
if it has a better θ (i.e. if θ ≤ θ0). The rules also extend the subtyping relation underneath arrow types and Σ-types.
Compared to the subtyping relation for the possible-world language (Figure 7.7), the effect-style subtyping relation can be simpler, because there are fewer features (@-types, mobile types) to take into account.