Notes on types and effects

Introduction

These notes recap our development of type and effect systems for typing languages with computational effects.

Syntax

The term syntax of our language is identical to the term language of the simply-typed λ-calculus, with the following extensions.

Expression Meaning
throw[t] Throw an exception (result type t)
try e1 catch e2 Execute e1; if it throws an exception, execute e2
get Get the value of the accumulator
put e Evaluate e, and store the result in the accumulator

The type system itself refines the type of functions to capture the effects of running them. Functions types are as follows; the remaining types are identical to the types of the simply-typed λ-calculus.

Type Meaning
$t \stackrel{z}{\to} u$ Functions from $t$ to $u$, with side effects $z$

Types

Our typing judgment will now take the form $\Gamma \vdash e : t \With z$, meaning that, with assumptions $\Gamma$, expression $e$ has type $t$ and may cause side effects $z$. The side effects will be represented as a set of possible side effects.

The typing rules themselves are as follows. Many of the rules are a straightforward extension of the existing rules for the simply-typed λ-calculus. Two representative samples are:

$$\frac{(x : t) \in \Gamma}{\Gamma \vdash x : t \With \emptyset} \qquad \frac{\Gamma \vdash e_1 : \Int \With z_1 \quad \Gamma \vdash e_2 : \Int \With z_2} {\Gamma \vdash e_1 + e_2 : \Int \With z_1 \cup z_2}$$

Variables have no side effects, so the effect annotation on the variable rule is the empty set. Addition propagates whatever effects took place in its arguments, so its effect annotation is the union of the effect annotations on its subexpressions.

$$\frac{\Gamma \vdash e : \Bool \With z \quad \Gamma \vdash e_1 : t \With z_1 \quad \Gamma \vdash e_2 : t \With z_2} {\Gamma \vdash \If{e}{e_1}{e_2} : t \With z \cup z_1 \cup z_2}$$

At type checking time, we do not know which branch of an if statement will be executed. So, our effect annotation for an if statement includes the effects of both branches. This will most likely be an over approximation; for example, consider the function

\ x : Int -> if isz x then throw[Int] else get

Executing this function will either throw an exception, or read from the accumulator, but definitely not both. Nevertheless, the typing of the body will be

$$x : \Int \vdash \If{\mathtt{isz}\,x}{\mathtt{throw[Int]}}{\mathtt{get}} : \Int \With \{ \mathsf{ex}, \mathsf{read} \}$$

reflecting both possible branches.

$$\frac{ }{\Gamma \vdash \mathtt{throw[t]} : t \With \{ \mathsf{ex} \}} \qquad \frac{\Gamma \vdash e_1 : t \With z_1 \quad \Gamma \vdash e_2 : t \With z_2} {\Gamma \vdash \Catch{e_1}{e_2} : t \With (z_1 \setminus \{ \mathsf{ex} \}) \cup z_2}$$

The throw term always produces an exception, so its effect annotation includes exceptions. The try..catch construct eliminates any exceptions caused in the first branch, but leaves other effects unchanged.

$$\frac{ }{\Gamma \vdash \mathtt{get} : \Int \With \{ \mathsf{read} \}} \qquad \frac{\Gamma \vdash e : \Int \With z} {\Gamma \vdash \mathtt{put}\;e : 1 \With \{ \mathsf{write} \} \cup z}$$

We distinguish between reading and writing effects; for example, the expressions $e_1 + e_2$ and $e_2 + e_1$ are equivalent if both read, but not if either writes.

$$\frac{\Gamma, x : t \vdash e : u \With z} {\Gamma \vdash \backslash x : t \to e : t \stackrel z \to u \With \emptyset} \quad \frac{\Gamma \vdash e_1 : t \stackrel z \to u \With z_1 \quad \Gamma \vdash e_2 : t \With z_2} {\Gamma \vdash e_1\,e_2 : u \With z \cup z_1 \cup z_2}$$

The side-effects in the body of a function take place when the function is executed, not when it is defined. Consequently, we extend function types to capture the side effects of the body, and add those captured side-effects to the effect annotation of applications.

$$\frac{\Gamma \vdash e : t \With z_1 \quad z_1 \subseteq z_2} {\Gamma \vdash e : t \With z_2}$$

Effect annotations are a conservative approximation of the actual side effects of the program. We can always weaken this assumption further. This rule is called subsumption. One use of this is in typing code that uses functions as arguments. For example, consider the following function

\ f : (Int -{ex}-> Int) -> f 4

where I write -{ex}-> as the ASCII rendering of $\stackrel{\{\mathsf{ex}\}}{\to}$. Intuitively, we expect f to possibly, but not necessarily throw exceptions. But now consider an attempt to use this function.

(\ f : (Int -{ex}-> Int) -> f 4) (\ x : Int -> x)

Now we have a problem. The function \x : Int -> x has type Int -{}-> Int, not Int -{ex}->, so this application is ill-typed. However, intuitively, we expect it to succeed. In this case, we could apply the subsumption rule, however, to conclude $x : \Int \vdash x : \Int \With \{ \mathsf{ex} \}$, and so the application can type.

Flavored exceptions

Our model of exceptions so far has been somewhat inexpressive: we can only throw one kind of exception. This might seem to lead to an uninteresting effect system as well. To make it more interesting, we consider a system in which we can include arbitrary values in thrown exceptions.

Our first change is that throw needs a value to throw, and catch needs to bind the caught value. More interestingly, however, we will allow catch to only catch exceptions of a particular type.

Expression Meaning
throw[u,t] e Throw an exception of type u (with result type t)
try e1 catch \ x : u -> e2 Execute e_2; if it throws an exception of type u, execute e2

To account for the variety of exceptions, we will now annotate the $\mathsf{ex}$ effect with the type the exception being thrown. Similarly, catch expression will now only filter exceptions of one type.

$$\frac{\Gamma \vdash e : u \With z} {\Gamma \vdash \mathtt{throw[u,t]\;e} : t \With z \cup \{ \mathsf{ex}(u) \}} \qquad \frac{\Gamma \vdash e_1 : t \With z_1 \quad \Gamma, x : u \vdash e_2 : t \With z_2} {\Gamma \vdash \Catch{e_1}{\backslash x : u \to e_2} : t \With (z_1 \setminus \{ \mathsf{ex}(u) \}) \cup z_2}$$

While the individual typing rules have become more detailed, the overall structure has not changed. In particular, catch passes on exceptions of types other than u just as it passed on non-exception effects before.