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