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.