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.
signature.asc
Description: PGP signature