Maxime Devos via General Guile related discussions <guile-user@gnu.org>
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.

Attachment: signature.asc
Description: PGP signature

Reply via email to