Wednesday, April 6, 2011

exception handling with defmacro

I've started work on a coroutine/thread scheduler, code named "Doom".  Doing some socket I/O has finally  snapped my 'missing feature' threshold - I really need exception handling to do it right.

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)))))

5 comments:

  1. A good history of exception handling in lisp: http://www.nhplace.com/kent/Papers/Condition-Handling-2001.html

    ReplyDelete
  2. And of course that code doesn't actually *work* as an exception handler because it doesn't capture the continuation of the handler. 8^) details!

    ReplyDelete
  3. Having trouble with the type safety of call/cc etc. This paper looks relevant: http://www.cs.uml.edu/~giam/91.531/Textbooks/GunterRR95.pdf
    "A generalization of exceptions and control in ML-like languages"

    ReplyDelete
  4. 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 ...'?

    ReplyDelete
  5. The "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