Careful with the catch/3 and throw/1

I found a little discrepancy. This is my test case:

test(X) :- catch((X=1;X=2), _, true).
test2(X) :- catch((X=1;X=2,throw(3)), X, true).

test3 :- test(X), test(Y), write(X-Y), 
         write(' '), fail; nl.
test3 :- test2(X), test(Y), write(X-Y), 
         write(' '), fail; nl.
test3 :- test(X), test2(Y), write(X-Y), 
         write(' '), fail; nl.
test3 :- test2(X), test2(Y), write(X-Y), 
         write(' '), fail; nl.

On GNU-Prolog, TauProlog, YAP and XSB I get:

?- test3, fail; true.
1-1 1-2 2-1 2-2 
1-1 1-2 3-1 3-2 
1-1 1-3 2-1 2-3 
1-1 1-3 3-1 3-3 

On SWI-Prolog 8.3.26 and ECLiPSe Prolog 7.0 I get:

?- test3, fail; true.
1-1 1-2 2-1 2-2 
1-1 1-2 
ERROR: Unhandled exception: 3

I guess it has to do with the timing, when the throw ball is unified
with the second catch/3 argument. After bindings are undone or before.
If the binding is still X=2, then a throw ball 3 doesn’t unify.

1 Like

Its a bit hard to read. SWI-Prolog has a documented deviation from the ISO standard for handling exceptions. If I recall correctly, ISO prescribes to undo before unifying the ball. SWI-Prolog looks for a catching frame without undoing anything. The reason for this is that this allows us to debug in a completely intact environment if an exception is not caught. It also allows for the exception hook to “decorate” the exception with context information depending on if or where the exception is caught. You do not want to pay the price of decorating the exception with a stack trace if you do e.g.

catch(open(File, read, In), error(_,_), fail)

But you do want this if the exception is something the user may want to debug. This is the case (typically) for uncaught exceptions as well as exceptions caught by catch_with_backtrace/3,.

1 Like

@jan,

I gather from this that an exception is expensive and costly, including performance wise.

Is there a way to have the effect of the control flow of an exception without that cost.

Dan

Exceptions are relatively costly. Most of the unavoidable exceptions are on stuff that is rather slow anyway and the cost of exceptions typically remains low. Note that catch/3 is not too bad. catch_with_backtrace/3 and the toplevel (dealing with uncaught exceptions) is relatively expensive as collecting the backtrace is expensive.

There are a couple of nasty cases. One concerns ISO number_chars/2 which is specified to raise an exception if the characters do not represent a number. Programs may need to wrap that into catch, even in cases where it will often raise an exception. The other concerns arithmetic evaluation, although exceptions are typically far less common here.

I think it is possible to implement catch/3 with close to zero overhead, at least for the scenario without an exception. Right now it pushes all arguments to the stack and do meta-calling on the goal (and recover in case of an exception). It could also compile its arguments …

Thank you Jan,

re: recover in case of an exception

what do you mean with recover – that the processing continues at the point of exception ?

I think it would be great if a variant can be called that only passes the argument without saving the unwound call stack(s) and as a means to enable a control flow that has to step out of the current processing …
Dan

Jan’s comment that it could be optimized to almost zero cost – i assume its not there yet …

Catching (or not) the backtrace is one. Without a flag means you’ll also get something useful on the first exception rather than having to re-run the program. The second is to be able to start the debugger at the place the exception happens rather than where it is caught. This has been discussed several times in the past. Yes, you can construct some cases where it matters. I’ve never seen one on the wild.

is it possible to “hand code” a jmp of kind – a barebone throw and catch – as it were …

Kind of a cut + dropping of the stack to an earlier frame (i guess) + new call

Thanks.

Yes, i will test that – also, how often it happens – but, the how often may depend on user data circumstances.

I meant performance – not semantics – which seemed to fit with what i expected.

I am very worried about the performance penalty introduced by making use of throw / catch – in a performance sensitive area.

I haven’t yet considered how to redesign this, but throw/catch is also a fitting idiom for what it was put to use.

Since I started to use it, quite some time ago – and, as you say, from an engineering perspective – I (try hard to) defer performance optimization to later – once i complete the functionality of the system.

Your discussion just let this worry buried in my stomach reemerge :slight_smile:

Also, i collect those discussions in my bookmarks, so i can get back to them, when ready.