Tag Archives: programming

Exception handling in Lisp, as seen from C++, Part 4

Now, just a bit more exposition before we get to the novel exception behaviour in Lisp.  Our example can now read in a file, print out the appropriate ratios, and give useful diagnostics in the event that the file is unreadable.  It also exits cleanly when it reaches the end of the input file.  But it’s still brittle.  The input file might not consist only of numbers, in which case the math operation will fail.

Knowing that a handler might be able to correct bad input, we write our changes accounting for that possibility.  We’ll create a new condition type, one that allows manipulation of its content.  Here’s the next version of the code:
 

;; Some examples of using conditions in Lisp code.
;;
(declaim (optimize (debug 3) (safety 3)))

(define-condition not-readable-number (error)
  ((object-seen         :initarg :object-seen
                        :initform nil
                        :reader get-object-seen)
   (was-corrected       :initform nil
                        :accessor get-was-corrected)
   (corrected-value     :initform nil
                        :accessor get-corrected-value))
  (:documentation "The condition that will be used to communicate
exceptions when trying to read numbers.")
  (:report (lambda (c s)
             (format s "The value \"~A\" could not be interpreted as a number."
                     (get-object-seen c)))))

(defun condition-demo (pathname)
  (format t "Starting to analyse file ~A~%" pathname)
  (handler-case
      (with-open-file (s pathname :direction :input)
        (analyse-stream s))
    (file-error (c)
      (format t "A file error was encountered while trying to analyse ~A.~%The error returned was:~%~A~%" pathname c))
    (end-of-file ()
      (format t "Successfully reached the end of the file.~%"))
    (condition (c)
      (format t "A non-file error was encountered while trying to analyse ~A.~%The error returned was:~%~A~%" pathname c))))

;; Goes through the stream, dividing entries one by the next
(defun analyse-stream (stream)
  (do ()
      (nil)
    (let ((num1 (get-next-number stream))
          (num2 (get-next-number stream)))

      (unless (numberp num1)
        (let ((msg (make-condition 'not-readable-number 
                                   :object-seen num1)))
          (signal msg)
          (unless (get-was-corrected msg)
            (error msg))
          (setf num1 (get-corrected-value msg))))

      (unless (numberp num2)
        (let ((msg (make-condition 'not-readable-number 
                                   :object-seen num2)))
          (signal msg)
          (unless (get-was-corrected msg)
            (error msg))
          (setf num2 (get-corrected-value msg))))

      (format t 
              "The ratio of ~A to ~A is ~A~%" 
              num1 num2 (/ num1 num2)))))

;; Returns the next object in the stream
(defun get-next-number (stream)
  (read stream))

Now, if we run the condition-demo function on this file:

1 2
4.1 9
zero one

we get the following output:

Starting to analyse file condition-input.txt
The ratio of 1 to 2 is 1/2
The ratio of 4.1 to 9 is 0.45555556
A non-file error was encountered while trying to analyse condition-input.txt.
The error returned was:
The value “ZERO” could not be interpreted as a number.

As we see, the value ‘zero’ was not interpreted as a number.  The condition was built and signaled, and the toplevel handler caught it, printed out its diagnostic information, and the program exited.

But maybe the programmer wants ‘zero’ to be recognised in some contexts.  Or maybe somebody, when creating the input file, put the numbers in double-quotes, so that it looked like this:

“1” “2”
“4.1” “9”
“zero” “one”

Well, we can set up a restart for this.  Maybe it is only valid on certain code paths, for certain filenames, but we can decide at function invocation time whether to try to fix up these values, or allow them to signal.  The code to do that will require some lengthy explanation, so it will come in the next post.

Exception handling in Lisp, as seen from C++, Part 3

So, we wrote a toy file reader and put in some simple error handling.  There are, however, other ways to fail than simply failing to open the file.  In Lisp, the conditions that are signaled must be derived from the condition type.  So we can put a general catcher for all errors not caught by file-error as follows:
 

;; Some examples of using conditions in Lisp code.
;;
(declaim (optimize (debug 3) (safety 3)))

(defun condition-demo (pathname)
  (format t "Starting to analyse file ~A~%" pathname)
  (handler-case
      (with-open-file (s pathname :direction :input)
        (analyse-stream s))
    (file-error (c)
      (format t "A file error was encountered while trying to analyse ~A.~%The error returned was:~%~A~%" pathname c))
    (condition (c)
      (format t "A non-file error was encountered while trying to analyse ~A.~%The error returned was:~%~A~%" pathname c))))

;; Goes through the stream, dividing entries one by the next
(defun analyse-stream (stream)
  (do ()
      (nil)
    (let ((num1 (get-next-number stream))
          (num2 (get-next-number stream)))
      (format t "The ratio of ~A to ~A is ~A~%" num1 num2 (/ num1 num2)))))

;; Returns the next object in the stream
(defun get-next-number (stream)
  (read stream))

Now, if a file-error occurs, one message is delivered, and if a non-file-error occurs, a different message is delivered.  In all cases, the function then exits cleanly, without further processing the file.

Of course, if we reach the end of the file, it’s probably OK for us just to exit cleanly with no error message.  An attempt to read past the end of the file will, by default, signal a condition of type end-of-file.  We can put a handler on that exception.  We now have the following code:
 

;; Some examples of using conditions in Lisp code.
;;
(declaim (optimize (debug 3) (safety 3)))

(defun condition-demo (pathname)
  (format t "Starting to analyse file ~A~%" pathname)
  (handler-case
      (with-open-file (s pathname :direction :input)
        (analyse-stream s))
    (file-error (c)
      (format t "A file error was encountered while trying to analyse ~A.~%The error returned was:~%~A~%" pathname c))
    (end-of-file ()
      (format t "Successfully reached the end of the file.~%"))
    (condition (c)
      (format t "A non-file error was encountered while trying to analyse ~A.~%The error returned was:~%~A~%" pathname c))))

;; Goes through the stream, dividing entries one by the next
(defun analyse-stream (stream)
  (do ()
      (nil)
    (let ((num1 (get-next-number stream))
          (num2 (get-next-number stream)))
      (format t "The ratio of ~A to ~A is ~A~%" num1 num2 (/ num1 num2)))))

;; Returns the next object in the stream
(defun get-next-number (stream)
  (read stream))

However, I promised to show a non-C++ style handling of conditions.  That’s what we’ll cover in the next post.

Exception handling in Lisp, as seen from C++, Part 2

We’ve talked a bit about conditions in Lisp, and how they differ from those in C++.  It’s helpful, though, to provide an example or two.

So, to demonstrate conditions, we’ll start with a simple program.  It opens a file on disc and reads pairs of numbers from it.  For each pair it displays the ratio of the first number to the second number.  Let’s call this the “My First Lisp Homework Problem” version of the code, because, while it does kind of work, it needs some improvements for real applications.

 
;; Some examples of using conditions in Lisp code.
;;
(declaim (optimize (debug 3) (safety 3)))

(defun condition-demo (pathname)
  (format t "Starting to analyse file ~A~%" pathname)
  (with-open-file (s pathname :direction :input)
    (analyse-stream s)))

;; Goes through the stream, dividing entries one by the next
(defun analyse-stream (stream)
  (do ()
      (nil)
    (let ((num1 (get-next-number stream))
          (num2 (get-next-number stream)))
      (format t "The ratio of ~A to ~A is ~A~%" num1 num2 (/ num1 num2)))))

;; Returns the next object in the stream
(defun get-next-number (stream)
  (read stream))

If you run condition-demo on a disc file that contains numbers, it will produce ratios and display them on the screen.  However, this code can’t handle anything unexpected.  If the pathname doesn’t exist, or if it doesn’t contain only numbers, or if the file isn’t infinitely long, it will, sooner or later, generate an unhandled error.

So, let’s start with the most obvious case.  The pathname is unreadable.  Either it doesn’t exist, or the process doesn’t have permission to open the file for reading.  In that case, a condition of the type ‘file-error is signaled.  Condition objects typically have a printable form, so we can print them in a format statement to help the user understand the problem.

 
;; Some examples of using conditions in Lisp code.
;;
(declaim (optimize (debug 3) (safety 3)))

(defun condition-demo (pathname)
  (format t "Starting to analyse file ~A~%" pathname)
  (handler-case
      (with-open-file (s pathname :direction :input)
        (analyse-stream s))
    (file-error (c)
        (format t "A file error was encountered while trying to analyse ~A.~%The error returned was:~%~A~%" pathname c))))

;; Goes through the stream, dividing entries one by the next
(defun analyse-stream (stream)
  (do ()
      (nil)
    (let ((num1 (get-next-number stream))
          (num2 (get-next-number stream)))
      (format t "The ratio of ~A to ~A is ~A~%" num1 num2 (/ num1 num2)))))

;; Returns the next object in the stream
(defun get-next-number (stream)
  (read stream))

Now, if the file is not present on disc, or is not readable, an error message is delivered to the screen, and the function exits cleanly.  That, though, is only the first of the things that can go wrong, and shows only the basic throw/catch style handling of conditions similar to those familiar to C++ programmers.  In a later post, we’ll discuss another way conditions can be handled.

Exception handling in Lisp, as seen from C++, Part 1

In earlier series of articles, I’ve been talking about Lisp language features as they might appear to a C++ programmer.  I went over the power of Lisp macros, and talked about object-oriented programming in Lisp.

Now, we move on to exception handling, referred to in Lisp as conditions.  In the simplest case, conditions look like C++ exceptions.  The programmer wraps a piece of code in a “handler-case” block, the equivalent of try{} in C++.  Somewhere down the call stack, a function throws the exception, and the stack unwinds, looking for an exception handler at each level.  When it finds one, the unwinding stops, the handler is executed, and code continues after the handler (unless the handler itself throws another exception and the stack unwinding proceeds anew).  That’s what the simple case looks like in Lisp, but that’s not the true story, it’s just a one common programming model for conditions.

So, what really happens?  Well, conditions can be addressed by handlers or by restarts.  When a condition is signaled, the environment in which the code is executing is examined.  If the closest handler is a restart, the appropriate function is invoked.  Crucially, Lisp does not unwind the stack in this instance.  The restart runs as if called as a function from the point where the error occured (but with the restart handler unbound, so it won’t be called recursively if the restart signals the exception again).  The restart can, in fact, fix the error and allow flow to continue after the signal (the equivalent of the C++ throw).

With this available, the programmer can, at one point in the code, decide what to do about conditions signaled within the context.  (S)he can decide to operate on the error and then allow the code to continue from the point where the error was signaled, or cause the code to unwind the stack to a handler, or do nothing, and let the signals be controlled by the enclosing context.  While the code that signals the error generally has to be written with the possibility of restarts in mind, it does provide an interesting way to handle exceptional cases.

I will be providing examples in subsequent posts.

Formatting code for the blog

In case people were wondering how the code I’ve been posting has been formatted, here’s the procedure.

This blog is running on a WordPress installation.  I’ve installed the “Raw HTML” plugin.  This allows me to insert HTML directly into postings by surrounding them with “[raw]” and “[/raw]” in the text input mode.

I program in emacs, so I just needed an emacs package to convert the buffer to HTML.  I started out trying to use the built-in htmlfontify-buffer function, but it produced HTML with CSS elements that affected the rest of the page.  So, after looking around a bit, I settled on htmlize, which is available at this location.

So, load the code to be rendered, load the htmlize library, and run htmlize-buffer.  This creates a new buffer holding the HTML version of the displayed text, ready to be copied into the blogging software.