Exceptional syntax

A nice paper apropos of tail calls and exceptions:
Nick Benton and Andrew Kennedy. 2001. Exceptional syntax. Journal of Functional Programming 11(4): 395-410.

From the points of view of programming pragmatics, rewriting and operational semantics, the syntactic construct used for exception handling in ML-like programming languages, and in much theoretical work on exceptions, has subtly undesirable features. We propose and discuss a more well-behaved construct.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

What am I missing?

try-in-unless can obviously be implemented using let and handle:


(*) try x <= M in P unless H = let x <= (handle M unless H) in P


This equivalence is easily provable with the big-step semantics given in the paper. Moreover, Benton and Kennedy give this exact expression in page 12.

The authors state 3 reasons to prefer the try construct over the handle:
1. Progammability: the try construct is more elegant in some situations.
2. Commuting convertions (optimization rewrite transformations). There are situations in which rewrite is wanted, but impossible.
3. Small-step operational semantics. There are no obvious small-step operational semantics for handle, but try has them.

I fail to see how, in light of (*), these reasons are valid. However, since I lack the required knowledge in commuting conversions, I don't trust my treatment of point 2.

1. Programmability: As handle can be viewed as syntactic sugar for let+handle, the programmability issue is moot: implement try using the more primitive constructs.

2. Conversions: I lack technical knowledge here and would be grateful for some more knowledgeable opinion.

Since conversions use pattern-matching anyway, can't we just substitute the try with handle in the conversions (fig. 2, page 8), and obtain more complex conversions that perform the same reduction?

3. Semantics: The handle construct can also be given small-step semantics (the transitions for the other constructs are as usual):


V handle H -> V
(raise e) handle H -> H(e)
E[raise e] -> raise e

where:

E[*] = (* M)
= ((\x.M) *)
= let x <= * in P

Obviously I'm missing something here, and I feel it's not just my incomplete knowledge of rewrite transformations.

Incorrect premise

Hopefully you've figured it out by now, one way or another, but your "obvious" implementation (*) is obviously incorrect. The right side always executes P (unless there is an unhandled exception) whereas the left side only executes P if M throws no exceptions.