Re: Couple of questions regarding handling exceptions

Maxime Devos via General Guile related discussions <[email protected]>
Newsgroups gmane.lisp.guile.user
Message-ID <[email protected]>
On 5/02/2025 1:38, Tomas Volf wrote:
> Hello,
>
> I would like to dispatch exceptions based on errno, in particular to
> return #f on ENOENT, and re-raise the exception otherwise.  My current
> (working as far as I can tell) solution is:

>    (with-exception-handler
>        (λ (exc)
>          (and (not (= ENOENT (car (list-ref (exception-args exc) 3))))
>               (raise-exception exc)))
>      (λ ()
>        (with-input-from-file (path realm query) read))
>      #:unwind? #t
>      #:unwind-for-type 'system-error)

This doesn't answer your question, but do note that this doesn't only 
catch exceptions of 'with-input-from-file', but also from 'path' and 
'read'.I see no reason for 'read' to produce 'ENOENT', but 'path' 
conceivably might (e.g. if it uses information recorded in a bunch of 
files, and some required info/file hasn't been read into cache yet).

Exceptions definitely do have their uses, but if you expect an exception 
to happen right after it is produced, then exceptions tend to get in 
your way and it would be more convenient if it was just a regular return 
value in some way (e.g. 'Either a Errno' in Haskell).

For this, I'd recommend defining a general procedure and general macro

;; (maybe add optional 'filter' argument to only consider certain errno, 
;; and let other errno through as an exception?) (define 
(%handle-system-error thunk success error) ;; do (thunk), in case of a 
system-error, do (error errno) as a tail-call, on success ;; do (apply 
success [return values of thunk]) ;; let other exceptions pass-through 
[...])

;; (handle-system-error (some-syscall-like-thing bla bla) ((result 
result2) [do stuff]) (errno [do other stuff])

(define-syntax handle-system-error [...])

and defining your own variant of 'with-input-file' that uses 
'handle-system-error'

Given how common such situations are, and how convenient being able to 
treat exceptions as non-exceptional situations occasionally can be, I 
think something like 'handle-system-error' would be good to have in 
Guile itself.

> However, I do not consider it very elegant, especially this part:
>
> --8<---------------cut here---------------start------------->8---
> (car (list-ref (exception-args exc) 3))
> --8<---------------cut here---------------end--------------->8---
>
> Is there better way to do this?

I recommend 'guard'. It let you catch exceptions as condition objects 
(well, objects in general since _technically_  you can raise other 
objects as well). And, you can do some filtering (more general than only 
on type), so instead of re-raising other exceptions, you can simply not 
catch them. It is also more straightforward to access 'fields' of 
condition objects, since they are record types instead of list structures.

For that part in particular, condition objects (which for no mentioned 
reason have been renamed to exception objects, and for no mentioned 
reason some condition types have been renamed, even though SRFI and RnRS 
conditions predate (ice-9 exceptions)) are convenient - if, 
hypothetically, a &system-error exception existed, then you could 
extract the errno with a hypothetical 'system-error-errno' procedure.

It doesn't seem like 'system-error' has been converted to a condition 
type yet, so you would need to add this to Guile first. (There is some 
machinery for automatic conversion between 'throw/catch' exception lists 
and condition objects - the former are implemented in terms of the 
latter IIRC.)

> Second question is how to print exceptions including a stack trace.  I
> noticed there is display-error, however it takes a frame argument and I
> am not sure where to get it.  I did not found any exception-frame
> procedure.  However since the default exception-handle *is* able to
> print the stack on exception, I assume it is possible.  Any tips?

There is no 'exception-frame', since frames aren't part of condition 
objects. Frames are also (unless that has been reimplemented) rather 
ephemeral - they are essentially represented as stack pointers, and 
after unwinding those aren't valid anymore.

That said, this could have been implemented differently with a spaghetti 
stack, where I think duplicating a stack region can be made almost zero 
cost (*)(***). So then stacks _could_ perhaps be cheaply added to 
condition objects, though if the condition object is kept around for a 
while you would need an option to remove the stack from the condition, 
to allow GC to collect it.)

How to print stack: do #:unwind? #false and put (backtrace) or similar 
in the exception handler. Note that stack copies aren't recorded in the 
exception object, so you need to do #:unwind? #t if you want the 
location where the exception was raised, instead of the location of 
'with-exception-handler'.

(*) not including the cost of having a spaghetti stack in the first 
place - I don't know the cost of that.
(**) thinking about it, I'd expect delimited continuations to do similar 
things already - I mean, delimited continuations seem to work nicely for 
Guile-Fibers. Maybe you could just capture a continuation in the 
exception handler, and afterwards inspect the continuation? Maybe even 
_today_? (***)
(***) If it's sufficiently efficient, perhaps Guile could automatically 
record the stack in the condition (with some option to enable/disable 
this, for GC reasons).

Best regards, Maxime Devos
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.