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);; for `try-thunk'
(owl thread) ;; for `example'
(owl proof)
)
define (try* f)
(values (try-thunk f (λ (out) (f* #n out)) 'try*) #f))))
(call/cc2 (λ (f*) (
define-syntax try
(syntax-rules ()
(exp ...)
((try
(try* (λ ()begin
(exp ...))))))
/ 10 0))))
(lets ((v err? (try (if err?
("try failed with: " err?)
(print "try returned: " v)))
(print
+ 10 1))))
(lets ((v err? (try (if err?
("try failed with: " err?)
(print "try returned: " v)))
(print
10 = 11))))
(lets ((v err? (try (example if err?
("try failed with: " err?)
(print "try returned: " v))) (print
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 ; ---+ this lambda can break back to call/cc2 |
(λ (out) (f* #n out)) ; which is just a call/cc that returns 2 values |
'try*) #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)
(2) ; if the call succeeds, it returns the actual outcome (unboxed from the tuple)
(ref outcome ; elsewise, it calls fail-fn with the full outcome tuple
(fail-fn outcome)))) ; (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)))
(cadr l) " failed: " (fold (λ (a b) (string-append a b " ")) "" (cddr l)))))
(print (
_)
(λ (10))))
(lets ((v err? (try (errorable if err?
(
(print-error err?)
(print v)))0))))
(lets ((v err? (try (errorable 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