5  Exceptions

Hi Chris,

As Paul Steckler points out, what you want is the construct
WITH-HANDLERS.  But you asked for an actual transliteration; here's a
pretty close approximation.  As you will see, WITH-HANDLERS is a bit
messy in the raw, but you can easily build abstractions around it to
suit your needs, and you rarely need its whole power.

> >>> def divider(x):
> ...     return lambda y : y / x
> ...
> >>> halver = divider(2)
> >>> halver(9)
> 4 #integer division is the default, but that's changing...
> >>> halver(4)
> 2

Here's the equivlent code.  I use ==> to denote output.

  (define (divider x)
    (lambda (y)
      (/ y x)))

  (define halver (divider 2))
  (halver 9)
  ==>
  9/2  ;; DrScheme actually prints            1
       ;; something that looks             4 --- 
       ;; more like                           2
       ;; but "copy" turns this 
       ;; into 9/2 for easy pasting

  (halver 4)
  ==>
  2

> >>> bomber = divider(0)

  (define bomber (divider 0))

> >>> bomber(4)

  (bomber 4)

> Traceback (most recent call last):
>   File "<stdin>", line 1, in ?
>   File "<stdin>", line 2, in <lambda>
> ZeroDivisionError: integer division or modulo by zero

  ==>
  /: division by zero
  ;; DrScheme also highlights the offending expression,
  ;; and displays a bug; clicking on this gives you a backtrace

> and I can catch exceptions in a very straightforward way:
> 
> >>> try:
> ...     bomber(4)
> ... except:
> ...     print "you moron -- something went wrong"
> ...
> you moron -- something went wrong
> 
> and I can choose which class of exceptions I want to catch
> 
> >>> try:
> ...     bomber(4)
> ... except ArithmeticError:
> ...     print "something went wrong with the arithmetic"
> ...
> something went wrong with the arithmetic
> 
> and even which subclass of ArithmeticError I want to catch
> 
> >>> try:
> ...     bomber(4)
> ... except ZeroDivisionError:
> ...     print "You tried to divide by zero!"
> ...
> You tried to divide by zero!

PLT Scheme uses a slightly different exception subclass hierarchy, but
the principle is the same.  Here's an example:

  (with-handlers ([exn:application?
		   (lambda (exn)
		     "You made an application error")])
    (bomber 4))
  ==>
  "You made an application error"

The "parts of speech" are

  (with-handlers ([PREDICATE HANDLER])
    BODY)

which of course you can generalize to

  (with-handlers ([PREDICATE0 HANDLER0]
                  [PREDICATE1 HANDLER1]
                  ...)
    BODY)

In the example above, I've used the built-in Scheme function
EXN:APPLICATION? as the predicate.  The handler is a procedure of one
argument, the exception "package".  Depending on the exception, you
have different operators that unbundle the package.

Here's a little function that can test different handlers:

  (define (try-different-exns exn-predicate message)
    (with-handlers ([exn-predicate (lambda (exn)
                                     message)])
      (bomber 4)))

Using this:

  (try-different-exns exn:application:divide-by-zero? 
		      "You tried to divide by zero!")
  ==>
  "You tried to divide by zero!"

  (try-different-exns exn:application? 
		      "You made an application error")
  ==>
  "You made an application error"

  (try-different-exns void 
		      "something went wrong")
  ==>
  "something went wrong"

Notice that in the last example, I use the "predicate" VOID.  VOID is
a function that accepts any number of arguments (zero or more) and
returns the "void" value, which is non-false.  Because this value is
non-false, PLT Scheme invokes the corresponding handler.  Therefore,
this is useful both for testing/prototyping and as a "catch-all"
(though in production code, it would be better to explicitly write
down the identity function here).

To get a list of the predicates, go to Help Desk (in DrScheme, it's in 
the Help Menu) and search for "exception".  You'll get an entry

  _exceptions,_primitive_hierarchy_ in "Primitive Exceptions"

Select this.  This lists the exception hierarchy as well as the fields 
available for use in the handlers.  For instance, the document says, 

  exn : not instantiated directly
    ...
    application : not instantiated directly 
    fields: value -- the error-specific inappropriate value (type: value) 
    ...
      divide-by-zero : divide by zero; application-value is always zero 

The text above the table tells us that the predicate name is

  exn:application:divide-by-zero?

and the field

  exn:application-value

of the exception package will always be 0.  Indeed:

  (with-handlers ([exn:application?
                   (lambda (exn)
                     (exn:application-value exn))])
    (bomber 4))
  ==>
  0

You can simplify the above code to

  (with-handlers ([exn:application?
                   exn:application-value])
    (bomber 4))
  ==>
  0

which shows that you don't always need to write (lambda (exn) ...).

Hope that helps get you started.  Indeed, you don't need custodians,
threads, parameters, etc.  We will produce more tutorial material on
this in the future.

Shriram