Here's my first attempt. The basic syntax is
(try <body> except <exception-patterns>)
.If this works, it'll be a great example of the huge advantages of the lisp syntax and macros - no changes made to the compiler to support it!
;; -*- Mode: Irken -*-
(include "lib/core.scm")
(include "lib/random.scm")
;; can we implement an exception system using the macro system?
;; XXX eventually we'll need to change call/cc to swap out exception
;; handlers, which probably means making a 'modern' call/cc with
;; dynamic-wind, etc.
(define (base-exception-handler exn)
(error1 "uncaught exception" exn))
(define *the-exception-handler* base-exception-handler)
(define (raise exn)
(*the-exception-handler* exn)
)
;; what kind of values do we want for exceptions, and how they're caught/compared?
;; python: started with strings, went to objects of the Exception class.
;; scheme: SRFI-12 includes something that looks like property lists?
;; sml: string refs? (in sml/nj according to CwC)
;; ocaml: declared exception datatypes.
;; [alternate for ocaml: http://dutherenverseauborddelatable.wordpress.com/downloads/exception-monads-for-ocaml/
;; sounds a little like my 'fourth idea']
;; my first temptation is to just use symbols, and stay simple
;; second idea is to use a record, which theoretically gives you the
;; ability to attach other kinds of data, which could be very
;; convenient. Example: (raise BadFD) => {exception='BadFD ...}
;; third idea: hardcore - define a global exception type, force the user to
;; extend it. [could be made easier by compiler hacks that allow you to
;; extend the type anywhere in the source?]. Code would have to wildcard
;; exceptions it doesn't know about? [or is that the definition of handing
;; it upstream?]
;; fourth idea: use records (or polymorphic variants) to define unique
;; exception names *and* types. For example: (raise (BadFD
;; current-fd)) would type as {BadFD=int ...} this would make it
;; more like the SRFI-12 property-list thing (unless I misunderstand
;; what SRFI-12 is all about). Hmmm... maybe we could extend the
;; exception handler by extending the record?
;; It'd be nice if the type system can tell us what exceptions are possible
;; at any given point in the code... is this maybe impossible due to the
;; dynamic (vs static/lexical) nature of exception handling?
;; the more I think about the 'fourth idea' the less reason I see to
;; restrict the type of exceptions. Syntax-wise, the following macro
;; does not enforce any restrictions. It's possible though that only
;; the polymorphic-variant-scheme will survive type checking.
;; this is the first time I've tried to 'accumulate' pieces in a macro.
;; it's possible that there's a better idiom that I just haven't discovered yet.
;; should really look to see how the scheme folks do this (or do they just avoid
;; it all by wrapping everything in verbose s-expressions).
(defmacro try
;; done accumulating body parts, finish up.
(try (begin body0 ...) <except> exn-match ...)
-> (let (($old-hand *the-exception-handler*))
(set!
*the-exception-handler*
(lambda ($exn)
(set! *the-exception-handler* $old-hand)
(match $exn with
exn-match ...
_ -> (raise $exn))))
(let (($result (begin body0 ...)))
(set! *the-exception-handler* $old-hand)
$result))
;; accumulating body parts...
(try (begin body0 ...) body1 body2 ...) -> (try (begin body0 ... body1) body2 ...)
;; begin to accumulate...
(try body0 body1 ...) -> (try (begin body0) body1 ...)
)
(define (random-barf)
(if (= (logand (random) 1) 1)
(raise (:OtherException 99))
7))
(define (thing)
(try
(let loop ((n 0))
(if (= n 100)
(raise (:MyException 12))
(begin
(random-barf)
(loop (+ n 1)))))
except
(:MyException value) -> value
(:OtherException _) -> 9
))
(thing)
Here's what the macro expansion looks like for
thing
:(define (thing) (let (($old-hand *the-exception-handler*)) (begin (set! *the-exception-handler* (lambda ($exn) (begin (set! *the-exception-handler* $old-hand) (%fatbar #f (%nvcase nil $exn (MyException OtherException) (1 1) ((let-splat ((m9 (%nvget (:MyException 0 1) $exn))) (let_subst (value m9) value)) 9) (%match-error #f)) (raise $exn))))) (let (($result (letrec ((loop (function loop (n) (if (= n 100) (raise (:MyException 12)) (loop (+ n 1)))))) (loop 0)))) (begin (set! *the-exception-handler* $old-hand) $result)))))
A good history of exception handling in lisp: http://www.nhplace.com/kent/Papers/Condition-Handling-2001.html
ReplyDeleteAnd of course that code doesn't actually *work* as an exception handler because it doesn't capture the continuation of the handler. 8^) details!
ReplyDeleteHaving trouble with the type safety of call/cc etc. This paper looks relevant: http://www.cs.uml.edu/~giam/91.531/Textbooks/GunterRR95.pdf
ReplyDelete"A generalization of exceptions and control in ML-like languages"
And the 'delimcc' library for OCaml talks about how to implement the operators described in that paper. I'm still trying to figure out why they need 'new_prompt' in addition to 'set x in ...'. Why not combine new_prompt and 'set x in ...'?
ReplyDeleteThe "catch me if you can" ocaml library has an interesting approach, that I could probably emulate with Irken. One aspect gives me pause: the type of exception-handling functions becomes annotated with the exceptions that it handles. Good/Bad/Ugly, this makes me nervous. Also, if you want to read that link you probably need to turn off javascript, otherwise the source boxes are unreadable. [tried 3 browsers]. http://dutherenverseauborddelatable.wordpress.com/downloads/exception-monads-for-ocaml/
ReplyDelete