Go-like error handling in Owl Lisp

I like lisps for the ability to just "hack in" additional language features.

Owl already includes a try function, but i don't really like that i cannot catch the error message to a variable by default.

This post shows a simple try function that returns (values value err?).

(import
 (owl toplevel)
 (owl thread) ;; for `try-thunk'
 (owl proof)  ;; for `example'
 )

(define (try* f)
  (call/cc2 (λ (f*) (values (try-thunk f (λ (out) (f* #n out)) 'try*) #f))))

(define-syntax try
  (syntax-rules ()
    ((try exp ...)
     (try* (λ ()
             (begin
               exp ...))))))

(lets ((v err? (try (/ 10 0))))
  (if err?
      (print "try failed with: " err?)
      (print "try returned: " v)))

(lets ((v err? (try (+ 10 1))))
  (if err?
      (print "try failed with: " err?)
      (print "try returned: " v)))

(lets ((v err? (try (example 10 = 11))))
  (if err?
      (print "try failed with: " err?)
      (print "try returned: " v)))

When ran as ol file.scm it yields:

try failed with: #[error #<function> division by zero  (/ 10 0)]
try returned: 11
try failed with: #[error #<function> example does not hold:  (10  evalutes to values  (10))]

As we can see, err? is a tuple that (in case of an error) holds all of the data we can get about it.

what

Let's take a closer look at the implementation of try*:

;; f → (values v err?)
(define (try* f)
  (call/cc2 ; <--------------------+
   (λ (f*)                    ;    |
     (values  ;  <-----------------+-------------------------------------------------+
      (try-thunk              ;    |                                                 |
       f                      ;    |                                                 |
       (λ (out) (f* #n out))  ; ---+ this lambda can break back to call/cc2          |
       'try*)                 ;      which is just a call/cc that returns 2 values   |
      #f))))                  ; elsewise, these values are returned -----------------+
                              ; they contain the return value of f and #f as a no-error indication

The things become clearer when we take a look at how try-thunk is implemented (in owl/thread.scm):

(define (try-thunk thunk fail-fn id)
  (link id)
  (thunk->thread id thunk)
  (let ((outcome (ref (accept-mail (λ (env) (eq? (ref env 1) id))) 2)))
    (if (eq? (ref outcome 1) 'finished)
        (ref outcome 2)         ; if the call succeeds, it returns the actual outcome (unboxed from the tuple)
        (fail-fn outcome))))    ; elsewise, it calls fail-fn with the full outcome tuple
                                ; (which we catch later to the err? variable)

why

Well, we can now yell at the user in a customized manner:

;; ...

;; this is a function that may throw an error
(define (errorable x)
  (if (= x 0)
      (error "you dummy dum dum" "you can't divide by 0")
      (/ 42 x)))

;; this is a custom error tuple printer
(define (print-error e)
  (let ((l (tuple->list e)))
    (print (cadr l) " failed: " (fold (λ (a b) (string-append a b " ")) "" (cddr l)))))

(λ (_)
  (lets ((v err? (try (errorable 10))))
    (if err?
        (print-error err?)
        (print v)))
  (lets ((v err? (try (errorable 0))))
    (if err?
        (print-error err?)
        (print v)))
  0)

This program (assuming try was defined beforehand), ran with ol -r file.scm yields:

21/5
#<function> failed: you dummy dum dum you can't divide by 0 

resources