ioremap.net

Storage and beyond

LISP error handling gotcha.

[1]> (defun resolve-host-name (addr)
  (handler-case (hostent-name (resolve-host-ipaddr addr))
    (t ()
       addr)))
RESOLVE-HOST-NAME
[2]> (resolve-host-name "1.2.3.4")
"1.2.3.4"
[3]> (resolve-host-name "195.178.208.66")
"tservice.net.ru"

Exception mechanism is a great extension to the whatever language, and I think LISP has one of the best realizations (and the first one actually). I’m not very familiar with the exceptions in C++ as long as with language itself, but iirc it is not (easily) possible return back to the calling point with some value determined by the exception handler. Even in Java with its

finally

section it is still less convenient. But I may be wrong of course :)

Above chunk of the code catches the error (all exceptions) and returns requested address itself, and when no error happend it returns resolved address.

Comments are currently closed.

4 Responses to “LISP error handling gotcha.”

  • Anonymous says:

    Both C++ and Java can catch an exception and then proceed from there.

    For instance, in C++:

    string resolve_host_name(string ipaddr)
    {
        try {
            return ipaddr_to_hostname_or_throw_exception(ipaddr);
        } catch (ResolverException &r) {
            return ipaddr;
        }
    }
    
  • zbr says:

    Yup, that simple, especially if used with

    catch (...) { }

    wildcards.

    But I meant to show a different thing, so called restarts. I emulated a restart here by returning actual address and not resolved name, but real restart system could allow to make this decision on the higher levels. So effectivel exception handling mechanims provides a way to signal an error, while restart (and in simple cases like I created in LISP and you showed in C++ it can be emulated in the exception block) allows to recover from the given error, and that restart can be handled on higher levels depending on the application design.

  • Anonymous says:

    What you’ve shown is exactly correspondent to the try-catch of other languages. Here’s a better example of how Common Lisp’s flow is powerful:

    (defun division (n d)
      (flet ((read-divisor ()
               (format t "~&Enter a new divisor: ")
               (list (read))))
        (loop
          (restart-case
              (return (/ n d))
            (use-value (new-d)
                :report "Use another divisor."
                :interactive read-divisor
              (setf d new-d))
            (continue ()
                :report "Return biggest value."
              (return most-positive-long-float))))))
    
    (defun div-0-to-1 (n d)
      (handler-bind ((division-by-zero #'(lambda (c)
                                           (use-value 1 c))))
        (division n d)))
    
    (defun div-infinity (n d)
      (handler-bind ((division-by-zero #'continue))
        (division n d)))
    
    (defun div-zero-error (n d)
      (handler-case
          (division n d)
        (division-by-zero (c)
          (error c))))

    Example interaction:

    : (division 1000 0)
    Error: Attempt to divide 1000 by zero.
    [condition type: DIVISION-BY-ZERO]
    Restart actions (select using :continue):
     0: Use another divisor.
     1: Return biggest value.
     2: Return to Top Level (an "abort" restart).
     3: Abort entirely from this (lisp) process.
    
    [1]: :continue 0
    Enter a new divisor: 2
    500
    
    : (division 1000 0)
    Error: Attempt to divide 1000 by zero.
    [condition type: DIVISION-BY-ZERO]
    Restart actions (select using :continue):
     0: Use another divisor.
     1: Return biggest value.
     2: Return to Top Level (an "abort" restart).
     3: Abort entirely from this (lisp) process.
    
    [1]: :continue 1
    1.7976931348623157d+308
    
    : (division 1000 0)
    Error: Attempt to divide 1000 by zero.
    [condition type: DIVISION-BY-ZERO]
    Restart actions (select using :continue):
     0: Use another divisor.
     1: Return biggest value.
     2: Return to Top Level (an "abort" restart).
     3: Abort entirely from this (lisp) process.
    
    [1]: :continue 2
    :
    
    : (div-0-to-1 1000 0)
    1000
    
    : (div-infinity 1000 0)
    1.7976931348623157d+308
    
    : (div-zero-error 1000 0)
    Error: Attempt to divide 1000 by zero.
    [condition type: DIVISION-BY-ZERO]
    Restart actions (select using :continue):
     0: Return to Top Level (an "abort" restart).
     1: Abort entirely from this (lisp) process.
    
    [1]: :continue 0
    :
    

    As you can see in

    div-0-to-1

    , the flow goes back to

    division

    with the value 1. In fact, we could do much more before, after or instead of

    use-value

    .

    In

    div-infinity

    , it goes back too, but in a simpler way just to signal division to return a pre-determined value.

    In

    div-zero-error

    , we simply catch the error and reissue it. This is the common try-catch with a throw inside. Normally, you wouldn’t write a try-catch-throw, because that’s the same as not using it at all, but here the try-catch part has the purpose of discarding the restarts, to make sure that the error is not continuable. This is why it uses

    handler-case

    instead of

    handler-bind

    .

    Another thing to notice is that when we use

    division

    from the prompt, we get an interactive debug prompt, while

    div-0-to-1

    ,

    div-infinity

    and

    div-error

    select the restart automatically. This shows that the restart mechanism is fit for both interaction and automation.

    PS:
    My first though when I wrote this was: the

    loop

    in there is ugly, and I might want to do this kind of thing more than once. Here’s Lisp’s power in action (Lisp in general, that is: eg. Common Lisp, Scheme):

    (defmacro retry-restart-case (restartable-form &rest clauses)
      `(loop
         (restart-case
             (return ,restartable-form)
           ,@clauses)))
    
    (defun division (n d)
      (flet ((read-divisor ()
               (format t "~&Enter a new divisor: ")
               (list (read))))
        (retry-restart-case
            (/ n d)
          (use-value (new-d)
              :report "Use another divisor."
              :interactive read-divisor
            (setf d new-d))
          (continue ()
              :report "Return biggest value."
            (return most-positive-long-float)))))

    This way, I hide the

    loop

    .

    PPS:
    In fact, the macro implementation doesn’t have to use

    loop

    , I was just lazily generalizing the structure into a macro. It can be done in terms of

    block

    and

    tagbody

    for reliability (they are treated specially by the evaluator/compiler) and faster macro expansion (

    loop

    itself is a rather complicated macro). These operators are mostly useful in macros, where you want to generalize “spaghetti” code (in fact, the conventional exception handling mechanisms are just obfuscated gotos) or add syntatic sugar:

    (defmacro retry-restart-case (restartable-form &rest clauses)
      (let ((retry (gensym)))
        `(block nil
           (tagbody
             ,retry
             (restart-case
                 (return ,restartable-form) ; same as (return-from nil ,restartable-form) 
               ,@clauses)
             (go ,retry)))))

    However, it has the same effect on code.


    Paulo Madeira

  • zbr says:

    I think I’m starting to forget these things, need to fresh bits out :)