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

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 e` |
Execute `e` ; if it throws an exception, execute `e` |

`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$ |

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.

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.

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 e` |
Execute `e_2` ; if it throws an exception of type `u` , execute `e` |

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.

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.