Re: Couple of questions regarding handling exceptions

Tomas Volf <[email protected]>
Newsgroups gmane.lisp.guile.user
Message-ID <[email protected]>
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 (application/pgp-signature, 853 B)
-----BEGIN PGP SIGNATURE-----

iQJCBAEBCgAsFiEEt4NJs4wUfTYpiGikL7/ufbZ/wakFAmemklYOHH5Ad29sZnNk
ZW4uY3oACgkQL7/ufbZ/wakTPxAAqOU1Wc0OHN2ovDojVsN+LV/ziNlFRJ0yUPOV
/06OfuKfEwOzCk0CB2QkVUzgtfuWYFtFqPAtbVDwzrODLZynfMSVE62sS5vU6HHQ
vzAKmPPvtuHqjMV0R3ZppPhtg+Pm3Sj2G4IXtozYIi7HImA5BHkbZZ/fa4/HzIhP
OukMwdXFe0khydu1EaGDHFZ7j5R2zT/B1VIQKoO88o4YBWunYaeWakjT39mh8fYr
tBlGoZSovVPeHvHvdppM+NXNdf2KCS0YQWTKEKWqLynq3jH4O3lll5VDCFZSxYKw
IQmwoHT8Ez53ZBjtXZyPSg8+b4VCB9dmVrPx6QtLDjBoAmESrZK6fH5ccq345maI
pdFaQsN9kXXjmfKBShzT8tx2gbSXhQ0UaKAwpMPahke8Okk8almSfnzjMBZjofvU
azAPGYQnWnn06FYGsICEmnpg4W3kYxJdqOTExji5lqroQGkTaFRfp21a0u2bad9F
KxX1d+eclrA9lyVSYP04/SpJXV1gaxKB6wWCCgWccn3p5KqBCnPNTe5JULySc0YL
UWsO+e6LwugWCj7XZkPBwa1kTrQDUzqTfzjzCDh/vLVilWUe/GtMFr7AtOB6hpKe
aoocmBIs0zjpSeJP0H+L5LS7pdAYnmJGHoEPhmWX3JT4mk23K8toZhbwHHXlYQkn
OPF0G3U=
=0tiQ
-----END PGP SIGNATURE-----
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.