Maxime Devos via General Guile related discussions <[email protected]> writes:
> 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).
Ah, that is a good point. In my case the `path' just string-appends
based on the arguments, but in general it is something to keep in mind.
>
> 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.
I have put together this:
--8<---------------cut here---------------start------------->8---
(define-syntax-rule (on-errno (errno ... alt) ... proc)
(λ (. args)
(with-exception-handler
(λ (exc)
(let ((actual-errno (system-error-errno
;; Bleh.
(cons 'system-error (exception-args exc)))))
(let loop ((errnos (list (list errno ...) ...))
(alts (list alt ...)))
(cond ((null? errnos) (raise-exception exc))
((memv actual-errno (car errnos)) (car alts))
(else (loop (cdr errnos) (cdr alts)))))))
(λ ()
(apply proc args))
#:unwind? #t
#:unwind-for-type 'system-error)))
(define-syntax-rule (with-on-errno errno-case ... body)
((on-errno errno-case ... (λ () body))))
--8<---------------cut here---------------end--------------->8---
It can be used in this ways:
--8<---------------cut here---------------start------------->8---
scheme@(guile)> (on-errno (ENOENT #f) call-with-input-file)
$10 = #<procedure 34a6eec8 at <unknown port>:79:0 args>
scheme@(guile)> ((on-errno (ENOENT #f) call-with-input-file) "/xx" read)
$11 = #f
scheme@(guile)> (with-on-errno (ENOENT #f) (call-with-input-file "/xx" read))
$12 = #f
scheme@(guile)> (with-on-errno (ENOENT 'no-file) (EPERM 'no-perm)
(call-with-input-file "/xx" read))
$13 = no-file
--8<---------------cut here---------------end--------------->8---
Not sure about the API. But seems to work.
>
>> 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.)
Adding &system-error is probably not an endeavor I currently have time
for, but it sounds like something that that would be useful. :)
> 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'.
I think I get the idea, I will explore this approach further.
Thank you and have a nice day,
Tomas
--
There are only two hard things in Computer Science:
cache invalidation, naming things and off-by-one errors.
signature.asc
Description: PGP signature
