Re: Readability of exception handling

Jan Wielemaker <[email protected]> Mon, 7 Apr 2014 17:18:31 +0200
Newsgroups gmane.comp.ai.prolog.swi
Message-ID <[email protected]>
On 04/07/2014 04:49 PM, Parker Jones wrote:

>  > Exceptions can be any term, but not variables. I.e., throw(_) itself
>  > is an error. catch(Goal, E, true) thus succeeds if Goal succeeds or
>  > throws an error. Iff goal succeeded, E is a variable.
>
> It does the trick, but it's a bit of a roundabout way of expressing things.

<snip>

> Perhaps it's due to my inexperience that I have to decrypt my own code
> to understand it.  Either that or it's simply not possible to write
> readable code beyond the simplest cases using ISO syntax exception
> handling.  I'd be a lot happier with a bit of syntactic sugar.

I showed one example of sugar.  You can invent your own less C-like
versions, such as

	on_exception(Goal,
		     [ Term => { Goal },
		       ...
	             ]).

etc.

> I'd be very interested to see something like a robust RPC server written
> in readable Prolog.  There's a lot that can go wrong with an RPC call
> and it all has to be handled safely.  Meanwhile I dread discovering a
> bug in my exception code in 12 months time and having to come back to
> it.

Hmmm. Most of the issue is avoiding leaking (stream-)handles. Use
(nested) setup_call_cleanup/3 for that. Jeff Rosewald (in library(tipc))
uses this:

   ==
   eventually_implies(P, Q) :-
	  setup_call_cleanup(P, (Foo = true; Foo = false), assertion(Q)),
	  Foo == true.

   :- op(950, xfy, ~>).

   ~>(P, Q) :- eventually_implies(P, Q).
   ==

so, you can write code like this

	open(Something, read, In) ~> close(In),
	do whatever you like, !.

It looks nice.  In particular, it is way nicer if you need multiple
handles.  For example:

   ==
   copy_file(From, To) :-
	open(From, read, In)   ~> close(In),
	open(From, write, Out) ~> close(Out),
         copy_stream_data(In, Out), !.
   ==

vs.

   ==
    copy_file(From, To) :-
        setup_call_cleanup(
	   open(From, read, In),
	   setup_call_cleanup(
	       open(From, write, Out),
	       copy_stream_data(In, Out),
	       close(Out)),
	   close(In)).
   ==

What I don't like is that ~> overloads the ! to both prune choice
points and reclaim resources.

Typically, I see myself using three patterns dealing with exceptions:

   - setup_call_cleanup/3 to reclaim resources.
   - catch(Goal, Error, true) typically to catch errors and send them
     over some communication channal.
   - catch(Goal, Error, print_message(warning, Error)) to ignore (but
     warn) about some error and continue.

I rarely test on specific errors.  Occasionally on existence errors,
as in,

	catch(thread_signal(victim, abort),
	      error(existence_error(thread,_),_),
	      true).

I'm curious in real code where error handling must be more subtle.

	Cheets --- Jan