More careful exceptions in Guile

By Christopher Allan Webber on Sat 05 September 2015

So as I've probably said before, I've been spending a lot more time hacking in Guile lately. I like it a lot!

However, there is one thing that really irks me: error handling. Though a programmer in Guile has a lot of flexibility to define their own error handling mechanisms, really I think a language should be providing good builtin ways of doing so. Guile does provide some builtin methods, but I have problems with both of them.

The first is the more egregious of the two, and is a procedure known simply as error, which takes one argument: a string describing what went wrong. Usage looks like so:

(if (something-bad? thing)
  (error "You shouldn't have done that!"))

This is fast to toss through your code without thinking, but at serious cost. The problem is that this follows the "diaper pattern" (or "diaper antipattern?"). Guile provides a catch procedure, but if you try catching these errors, they are all thrown with the "misc-error" symbol, and there is no way to catch the right errors.

(catch 'misc-error
  ;; the code we're running
  (lambda ()
    (let ((http-response (get-some-url)))
      (if http-response
          ;; all went well, continue with our webby things
          (do-web-things http-response)
          ;; Uhoh!
          (error "the internet's tubes are filled"))))
  ;; The code to catch things
  (lambda _ (display "sorry, someone broke the internet\n")))

But wait... what if the user gave a keyboard interrupt and instead your database execution code caught it instead? I you can't catch errors precisely, things might bubble to the wrong place.

This is not an abstract problem; this happened to me in an extremely well written Guile program, Guix: I was working on adding a new package and had screwed up the definition, so somewhere up the chain Guix threw an error about my malformed package, but I didn't know... instead, when I was attempting to run the "guix package" command to test out my command, suddenly the "guix package" command disappeared entirely. Whaaaaat? I did some debugging and found a (catch 'misc-error) in the command line arguments handling code. Whew! Well, that usage of "(error)" got replaced with some more careful code, but what if I couldn't find it, or was a more green developer?

So, luckily, Guile does provide a better exception handling system, mostly. There's throw, which looks a bit like this in your code:

(catch 'http-tubes-error
  ;; the code we're running
  (lambda ()
    (let ((http-response (get-some-url)))
      (if http-response
          ;; all went well, continue with our webby things
          (do-web-things http-response)
          ;; Uhoh!
          (throw 'http-tubes-error "the internet's tubes are filled"))))
  ;; The code to catch things
  (lambda _ (display "sorry, someone broke the internet\n")))

Okay, great! This is much more specific, yay!

Except... it still kind of bothers me. Maybe I'm being overly pedantic here, but what if you and I both had 'json-error exceptions in our own separate libraries? The problem is (unlike in common lisp) there aren't module-specific symbols in Guile! This means we could catch someone else's 'json-error when we really wanted to catch our own.

Okay, maybe this is rare, but I really don't like running into these kinds of problems. I want my exception symbols to be unique per package, damnit!

So in the interest of doing so, let me present you with a terrible hack of scheme code (which like all other code content in this blogpost, I both waive under CC0 1.0 Universal (and also do waive any potential patent "rights") and also release under LGPLv3 or later, your choice):

(define-syntax-rule (define-error-symbol error-symbol)
  (define error-symbol
    (gensym
     ;; gensym can take a prefix
     (symbol->string (quote error-symbol)))))

Okay, it's kind of hacky, but what this does is give you a nice convenient way to define unique symbols. (Edit: turns out gensym can take a prefix, so the above code is even easier and less hacky now! Thanks for the tip, taylanub!) You can use it like so:

(define-error-symbol http-tubes-error)

(catch http-tubes-error
  ;; the code we're running
  (lambda ()
    (let ((http-response (get-some-url)))
      (if http-response
          ;; all went well, continue with our webby things
          (do-web-things http-response)
          ;; Uhoh!
          (throw http-tubes-error "the internet's tubes are filled"))))
  ;; The code to catch things
  (lambda _ (display "sorry, someone broke the internet\n")))

See? All you have to do is do a simple definition above and you have a unique-per-your-program error symbol (thanks to the gensym). Now if users want to catch your errors, but only your errors, they can import the error symbol directly from your package.

So the lesson from this post is: if you're going to use exceptions in your code, please be careful... and specific!

Update: Apparently I can't be the only one who finds the need for this; turns out that prompts (which have a similar "unwinding" property to exceptions) also take symbols, but usefully there's (make-prompt-tag) which does pretty much exactly the same thing as define-error-symbol above. So I must not be totally crazy!