Exceptions

TAPL builds up an exception mechanism in three steps: raising exceptions, handling exceptions, and exceptions returning values. Below are the semantics of these three mechanisms using evaluation contexts.

Raising exceptions

Syntax:

t ::= ...
      error

Evaluation:

E[error]  error          (E-ConError)  

Whereas the E-AppErr1 and E-AppErr2 rules in TAPL “unwinds” the evaluation context one layer at a time, this rule unwinds it all at once. (Actually, this rule is nondeterministic because it can unwind any part of the evaluation context. How would you modify the rules to make them deterministic?)

Handling exceptions

Syntax:

t ::= ...
      error
      try t with t

Now the evaluation rule for error should only unwind the evaluation context up to the nearest try, not all the way up to the top of the program as before. So we need two kinds of evaluation contexts. The first has the same definition as if we hadn’t added exceptions to the language:

F ::= [⋅]
      F t
      v F

The second can break through try:

E ::= F
      F[E]
      try E with t

Evaluation rules:

try v1 with t2  v1        E-TryV
try F[error] with t2  t2  E-TryConErr

Exceptions returning values

Syntax:

t ::= ...
      raise t
      try t with t

F ::= [·]
      F t
      v F
      raise F

E ::= F
      F[E]
      try E with t

Evaluation:

try v1 with t2  v1                E-TryV
try F[raise v11] with t2  t2 v11  E-TryConRaise

In pure lambda calculus

In “exception-passing style,” every term t is replaced with a sum. The inl values represent exceptions, and the inr values represent ordinary values.

A term that doesn’t raise an exception becomes an inr:

    𝒯⟦x⟧ = inr x
𝒯⟦λx. t⟧ = inr λx. 𝒯⟦t⟧

But a raise expression becomes an inl. As before, we define the translation just for variables:

𝒯⟦raise x⟧ = inl x

The translation of try is then:

𝒯⟦try t1 with t2⟧ = case 𝒯⟦t1⟧ of inl x  inr x,
                                  inr x  𝒯⟦t2 x⟧
                                  

Application of one variable to another is easy, as before.

𝒯⟦x1 x2⟧ = x1 x2

The translation of let captures the fact that if t1 raises an exception, then t2 is bypassed.

𝒯⟦let x = t1 in t2⟧ = case 𝒯⟦t1⟧ of inr x  𝒯⟦t2⟧, inl x  inl x

Now we can derive the more general rules for raise and applications.

𝒯⟦raise t⟧ = 𝒯⟦let x = t in raise x⟧
           = case 𝒯⟦t⟧ of inl x  inl x,
                          inr x  inl x
                                 
𝒯⟦t1 t2⟧ = 𝒯⟦let x1 = t1 in let x2 = t2 in x1 x2⟧
         = case 𝒯⟦t1⟧ of inl x  inl x,
                         inr x  (case 𝒯⟦t2⟧ of inl y  inl y, inr y  x y)