Wednesday, April 27, 2011

DOOM DOOM DOOM (a coro/thread kqueue-based scheduler)

I've created a new sub-project, code-named 'doom', for the coroutine-based cooperative threading system.  It uses kqueue(), of course.  Because it's better.  It wouldn't be too hard for someone from linux-land to make a version based on epoll.

In the doom directory, you'll find a simple http demo, and the obligatory echo server.

Exception handling certainly makes the code look a lot nicer.

I'm the best at space.

Friday, April 22, 2011

exception handling, take two

A bit tricky, but I found a way to implement try/except almost completely with defmacro.  I needed to add some extra typechecking around raise and try to ensure that all exception types are monomorphic.  I achieved this by having the compiler keep a global map of exception-name => tvar.   Seems to work...

While working on this I learned that the Scheme-style call/cc is not really type safe, and I need to use the SML version.  The main difference is the protocol - SML's callcc requires the use of a throw procedure since the continuation needs to carry type information:

(define (callcc p) : (((continuation 'a) -> 'a) -> 'a)
  (p (getcc)))

(define (throw k v)
  (putcc k v))




;; -*- Mode: Irken -*-

(include "lib/core.scm")
(include "lib/random.scm")

;; We use polymorphic variants for exceptions.
;; Since we're a whole-program compiler there's no need to declare
;; them - though I might could be convinced it's still a good idea.
;;
;; Exception data must be monomorphic to preserve type safety.
;;
;; <try> and <raise> are implemented as macros, with one extra hitch:
;;  two special compiler primitives are used to check that exception
;;  types are consistent: %exn-raise and %exn-handle

(define (base-exception-handler exn) : ((rsum 'a) -> 'b)
  (error1 "uncaught exception" exn))

(define *the-exception-handler* base-exception-handler)

(defmacro raise
  (raise exn) -> (*the-exception-handler* (%exn-raise #f exn))
  )

(defmacro try
  ;; done accumulating body parts, finish up.
  (try (begin body0 ...) <except> exn-match ...)
  -> (callcc
      (lambda ($exn-k0)
        (let (($old-hand *the-exception-handler*))
          (set!
           *the-exception-handler*
           (lambda ($exn-val)
             (set! *the-exception-handler* $old-hand)
             (throw $exn-k0
              (%exn-handle #f $exn-val
               (match $exn-val with
                 exn-match ...
                 _ -> (raise $exn-val))))))
          (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 (level0 x)
  (try
   (level1 x)
   except
   (:Level0 x) -> (:pair "level 0" x)
   ))

(define (level1 x)
  (try
   (level2 x)
   except
   (:Level1 x) -> (:pair "level 1" x)
   ))

(define (level2 x)
  (try
   (level3 x)
   except
   (:Level2 x) -> (:pair "level 2" x)
   ))

(define (level3 x)
  (try
   (match x with
     0 -> (raise (:Level0 x))
     1 -> (raise (:Level1 x))
     2 -> (raise (:Level2 x))
     3 -> (raise (:Level3 x))
     4 -> (:pair "no exception!" 99)
     _ -> (raise (:Bottom x))
     )
   except
   (:Level3 x) -> (:pair "level 3" x)
   ))


(printn (level0 0))
(printn (level0 1))
(printn (level0 2))
(printn (level0 3))
(printn (level0 4))
(printn (level0 5))

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