In Chapter 6, you learned how to interact with a user by sending text to and from the REPL. However, when people talk about “interacting with a user” these days, they’re usually referring to a user on the Web. In this chapter, you’re going to learn how to interact with users on the Web by building a web server from scratch. Since communications over a network are error prone by their nature, you’ll first learn how errors are handled in Lisp.
Any time you’re interacting with the outside world, as our web server will, unexpected things can happen. No matter how smart a modern computer network may be, it can never anticipate every possible exceptional situation. After all, even the smartest network can’t recover from some fool tripping over the wrong cable.
Common Lisp has a very extensive set of features for dealing with unexpected exceptional situations in your code. This exception handling system is very flexible, and it can be used to do things that are impossible with exception systems in most other languages.
If you’re writing a function and something goes horribly wrong, a Lisp function can notify the Lisp environment that a problem has been encountered. This is done by signaling a condition. What sort of things could go wrong? Maybe a function tried to divide by zero. Or maybe a library function received a parameter of the wrong type. Or maybe a socket communication was interrupted because you tripped over your network cable.
If you want to signal a condition directly, you can do so with the error
command. You would do this if a function you wrote detected a problem on its own—a problem so serious the program just could not continue normally. Using the error
command will interrupt your running Lisp program, unless you intercept the error elsewhere to prevent an interruption. Let’s signal a condition and print the message “foo” to describe the error:
> (error "foo")
*** - foo
The following restarts are available:
ABORT :R1 Abort main loop
>
As you can see, signaling this condition causes Lisp to interrupt our program, print the message “foo,” and show an error prompt at the REPL. (In CLISP, you can type :a
at this point to abort the program and return to the normal REPL.)
Most of the time your program signals a condition, it will probably not be because you called error
yourself. Instead, it will be because your program has a bug, or because you called a library function, and that function signals a condition. However, any time something prevents normal execution in your program, leading to a condition, your program will stop and show an error prompt such as in the preceding example.
In our first example, we passed a string describing the condition to the error
command. However, this text string just customizes the error message and doesn’t lead to a different “type” of condition. Common Lisp also allows you to have various types of conditions that can be handled in different ways.
A more sophisticated way to signal conditions is to first define a custom condition using define-condition
, as in the following example:
(define-condition foo () () (:report (lambda (condition stream) (princ "Stop FOOing around, numbskull!" stream))))
This is a typical example of creating a new type of condition, which we’ve named foo
. When this condition is signaled, we can supply a custom function that will be called to report the error. Here, we declare a lambda function for this purpose . Within the lambda function, we print a custom message to report the error .
Let’s see what happens when we trigger this new condition:
> (error 'foo)
*** - Stop FOOing around, numbskull!
The following restarts are available:
ABORT :R1 Abort main loop
>
As you can see, our custom message was printed. This technique allows the programmer to get a more meaningful error report, customized for the specific condition that was triggered.
When we create a condition with define-condition
, it’s given a name (such as foo
). This name can be used by the higher-level parts of our program to intercept and handle that condition, so it won’t stop the program’s execution. We can do this with the handler-case
command, as follows:
>(defun bad-function ()
(error 'foo))
BAD-FUNCTION >(handler-case (bad-function)
(foo () "somebody signaled foo!")
(bar () "somebody signaled bar!"))
"somebody signaled foo!"
The first thing we put inside a handler-case
command is the piece of code that may signal conditions that we want to handle .
In this example, the code we’re watching is a call to bad-function
. The rest of handler-case
lets us specify actions to perform if a particular condition occurs . When this code is run, bad-function
signals the foo
condition by calling (error 'foo)
. Usually, this would cause our program to be interrupted and lead to a error prompt at the REPL. However, our handler-case
command intercepts the foo
condition . This means that the program can keep running without interruption, with the handler-case
evaluating as “somebody signaled foo!” .
When an unexpected exception happens in a program, there is always a risk that it could bring down your program, or even cause damage to resources outside your program. Exceptions interrupt the regular flow of your code, and they may stop your code dead in its tracks, even while it’s in the middle of a sensitive operation.
For instance, your program may be writing to a file or to a socket stream when an unexpected exception happens. In this case, it is critically important that your program has an opportunity to close the file/socket stream and free the file handle or socket; otherwise, that resource may become locked indefinitely. If such resources aren’t cleaned up properly, the users may need to reboot their computer first before the resource becomes available again.
The unwind-protect
command can help us to avoid these problems. With this command, we can tell the Lisp compiler, “This piece of code must run no matter what happens.” Consider the following example:
>(unwind-protect (/ 1 0)
(princ "I need to say 'flubyduby' matter what"))
*** - /: division by zero The following restarts are available: ABORT :R1 Abort main loop >:r1
I need to say 'flubyduby' matter what >
Within the unwind-protect
, we divide by 0, which signals a condition . But even after we tell CLISP to abort, the program still prints its crucial message .
We can usually avoid calling unwind-protect
directly by relying on Common Lisp’s “with-
” macros; many of these call unwind-protect
themselves, under the hood. In Chapter 16, we’ll create our own macros to see how this is possible.
In the comic book epilogue at the end of the book, you’ll learn about an additional feature of the Common Lisp signaling system called restarts.
18.225.235.144