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 t1 with t2

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:

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 variant type that allows exception values.

             𝒯⟦x⟧ = <ok=x>
         𝒯⟦λx. t⟧ = <ok=λx. 𝒯⟦t]>
         𝒯⟦t1 t2⟧ = case 𝒯⟦t1⟧ of
                        <err=e>  <err=e>,
                         <ok=f>  (case 𝒯⟦t2⟧ of
                                      <err=e>  <err=e>,
                                       <ok=v>  f v)

For clarity, we first show how to translate raise x and try t1 with x2:

       𝒯⟦raise x⟧ = <err=x>
       
𝒯⟦try t1 with x2⟧ = case 𝒯⟦t1⟧ of
                        <err=e>  x2 e,
                         <ok=v>  <ok=v>

The general case is more complicated because of the possibility of exceptions being raised during the evaluation of both of the above.

       𝒯⟦raise t⟧ = case 𝒯⟦t⟧ of
                        <err=e>  <err=e>,
                         <ok=x>  <err=x>

𝒯⟦try t1 with t2⟧ = case 𝒯⟦t1⟧ of
                        <err=e>  (case 𝒯⟦t2⟧ of
                                       <err=e'>  <err=e'>,
                                       <ok=h>  h e),
                        <ok=v>  <ok=v>