[clean-list] Wish List, Part 2

Monique Wittebrood & Marco Kesseler m.wittebrood@mailbox.kun.nl
Tue, 01 Jan 2002 21:13:15 +0100


Happy new year to you all,

Time for a second item on my clean wish-list. Although I have not yet had
the time to have a close look at all the new Clean 2.0 goodies, I must say
that I am VERY pleased that all of the sources have been released including
the code generator sources (for a moment, I have even contemplated  porting
these to Clean, but I have not yet any reason to give this some priority).

On to my wish list, which still contains but one item:
- better interfacing with imperative (object-oriented) languages (at least
  allowing Clean to be called from these languages).

Here's another wish: exceptions

I got used to exceptions in languages like C++, where they bring separation
of error handling code from ordinary code. Even though C++ burdens the
programmer with additional work in order to use exceptions, they are really
quite useful in many cases. Many of these burdens can be avoided in
languages with automatic memory management, so in Clean exceptions can be
even more useful.

In some of my code, I have even started to used my own 'Try' type (see
below), because it already allows one to somewhat separate error handling
from ordinary code by hand.

:: Try x e
	= EXCEPTION e
	| RESULT x

throw e :== EXCEPTION e
return x :== RESULT x

Using case expressions, one can then write functions like:

scanChars :: [UniChar] State -> Try State String
scanChars [char : chars] state = case (scanUniChar state) of
	RESULT (c, state)
		| c == char
			-> scanChars chars state
			-> throw "no match"
	EXCEPTION e
		-> EXCEPTION e
scanChars [] state
	= return state

But I actually would have liked to say something like:

scanChars :: [UniChar] State -> State, Throws String
scanChars [char : chars] state
	try (c, state) = scanUniChar state
	| c == char
		= scanChars chars state
		= throw "no match"
scanChars [] state
	= state

Catching exceptions could be something along the line:

scanNameChars name state0
	try (c, state) = (scanUniChar state0)
	| isNameXmlChar c
		= scanNameChars [c : name] state
		= (toUniString (reverse name), state0)
	catch _
		= (toUniString (reverse name), state0)

More detail is outside the scope of this e-mail I guess. I do not have a
complete solution. A few additional remarks though:

- Perhaps forcing a function to throw only a single type is not all that bad:
  1) In C++ one sometimes ends up mapping different exceptions to a single
     type. In this way one avoids long sequences of catch statements.
  2) One can also throw an algebraic type, an Existential type, or
     a Dynamic in case one wants to throw 'different' types of
     exceptions.

- Above, the scope of the try is just the one line behind it. This is too
  limited. It should be possible to deal with exceptions in guards
  and in the expressions that the function evaluates to. Coming up with
  the right transformation rules is a nice job for the compiler writers I
  guess (but hey, now we got the sources).

- Above, in case a function has type "a, Throws b", it must either reduce to an
  expression of type a (in which case the compiler must wrap it in a return
  statement), or it must reduce to an expression that has type "a, Throws b"
  as well. This is too restricting. It should be possible to have components
  throw different types of exceptions and have a catch cascade for mapping
  these types of exceptions to the one (or the many?) listed in Throws
  specification.


Regards,
Marco