Debugging Lisp Part 4: Restarts

This is part four of Debugging Lisp. Here are the previous parts on recompilation, inspecting, and class redefinition. The next post on miscellaneous debugging techniques can be found here.

Many languages provide error handling as two distinct parts, throw and catch. Throw is the part that detects something has gone wrong and in some way signals that an error has occurred. In the process, throw creates an exception object which contains information about the problem. The other part, catch, takes the exception object signaled by throw and attempts to recover from the error.

The issue with throw/catch is that throw acts like an unconditional goto to the catch part. Because of this, all of the state information that is available when throw is used that is not given to the exception object is lost. This becomes problematic if the code that catches the error wants to use some information about what happened when the error occurred in order to recover.

As an example, let’s say you are implementing a library which takes several files and parses a list of numbers from each one. One way to implement this library is as two functions. The first function, read-file, will read the contents of a single file and return a list of the results. The second, read-files, will take a list of files and return a list of the contents of each one. Here is what the code for those two functions might look like if they did not have any error handling:

(defun read-file (file)
  (with-open-file (in file :direction :input)
    (loop for line = (read-line in nil in)
          until (eq in line)
          collect (parse-integer line))))

(defun read-files (files)
  (loop for file in files
        collect (read-file file)))

To test the library you have two files. The first file contains the numbers 5, 10, 15, 20, 25 and the second contains 5, 10, 15, 20, a, 30, 40. In order to make sure your library handles errors properly, you decided to put a line which is just “a” in the second file. As it stands, parse-integer will signal an error when it comes across this line. To make testing the library easy, you have stored a list containing the pathnames of the two files in the variable *files*. Here is what happens when you try running the library on the two files:

(read-files *files*)

=> ERROR

An error occurred due to the “a” in the second file. As the designer of the library you have to decide what should happen when a situation like this one comes up. Below are several different options you could choose from if your language only provided catch/throw.

Your first option is to just skip the entry that caused the error. To do this, you could use handler-case, Common Lisp’s version of catch:

(defun read-file (file)
  (with-open-file (in file :direction :input)
    (loop for line = (read-line in nil in)
          until (eq in line)
          when (handler-case (parse-integer line)
                 ;; C is the name being used to
                 ;; refer to the exception object.
                 (error (c)
                   (declare (ignore c))
                   nil))
          collect it)))

(read-files *files*)

=> ((5 10 15 20 25) (5 10 15 20 30 40))

Another option is to provide a dynamic variable1 which the user of the library can use to specify a value to be used in place of the malformed entry:

(defvar *malformed-value* nil)

(defun read-file (file)
  (with-open-file (in file :direction :input)
    (loop for line = (read-line in nil in)
          until (eq in line)
          when (handler-case (parse-integer line)
                 (error (c)
                   (declare (ignore c))
                   *malformed-value*))
          collect it)))

(let ((*malformed-value* :malformed))
  (read-files *files*))

=> ((5 10 15 20 25) (5 10 15 20 :MALFORMED 30 40))

A third option is to have read-files catch the error and skip the entire file with the malformed entry:

(defun read-files (files)
  (loop for file in files
        when (handler-case (read-file file)
               (error (c)
                 (declare (ignore c))
                 nil))
        collect it))

(read-files *files*)

=> ((5 10 15 20 25))

Your last option is to let the user of the library handle the exception themselves:

(handler-case (read-files *files*)
  (error (c) (do-something)))

To the user, this last option is somewhat useful because it gives them some flexibility into how the error is handled. As mentioned above, the problem with doing this is that it becomes difficult for the user to properly recover from the error. If the user just wanted to skip the one corrupted file, there is no easy way to for them to do that due to the fact that by the time their error handling code is ran, execution would have left read-files. This means all of the state information, such as the remaining files that need to be read from, is completely lost by the time their code catches the exception.

Another problem with catch/throw is that of the four possible ways above you could handle the problem, you only get to choose one of them. Any one of them is in conflict with all of the others. Again, this is because throw acts like goto. Once you decide where you are jumping to, you have no control over what happens next. And, if you let the user handle the error themselves, they have no easy way to handle the error gracefully since all of the state information is lost.

This is where restarts come in. In Common Lisp, catch is provided as two separate pieces: handlers and restarts. A handler is bound by the user of the library in order to specify what should happen when an exception is thrown and a restart is defined by the library in order to provide a recovery option to the user. If you are using a language that supports restarts, you could implement the first three options above as restarts. Then when a user is using the library, they will get to select which of those restarts they want to have run when an error occurs. If they do not want to use any of the restarts, they can run their own code instead. Here is the code for the file reading library, but reimplemented to support three different restarts, one for each of the first three ways to handle errors.

(defun ask (string)
  (princ string *query-io*)
  (read *query-io*))

(defun read-file (file)
  (with-open-file (in file :direction :input)
    (loop for line = (read-line in nil in)
          until (eq in line)
          when (restart-case (parse-integer line)
                 (use-value (value)
                   :report "Use a new value."
                   :interactive (lambda ()
                                  (list (ask "Value: ")))
                   value)
                 (skip-entry ()
                   :report "Skip the entry."
                   nil))
          collect it)))

(defun read-files (files)
  (loop for file in files
        when (restart-case (read-file file)
               (skip-file ()
                 :report "Skip the entire file."
                 nil))
        collect it))

;;; The three functions below are predefined
;;; handlers for the most common ways the user
;;; will interact with the restarts.
(defun skip-entry (c)
  (declare (ignore c))
  (invoke-restart 'skip-entry))

(defun skip-file  (c)
  (declare (ignore c))
  (invoke-restart 'skip-file))

(defun use-value-handler (value)
  (lambda (c)
    (declare (ignore c))
    (invoke-restart 'use-value value)))

A restart is defined with the macro restart-case, and invoked by the function invoke-restart. This is a bit of a simplification, but invoking a restart is effectively equivalent to jumping to the body of the restart from where the error was signaled. This means that all of the state stored on the stack before the restart was established is still available when the restart is invoked. This gives the user of the library much finer grained control over what happens when an error is thrown.

To specify what should happen, all the user needs to do is use the macro handler-bindHandler-bind takes an error type and a handler (which should be a function) to call when an error of that type is thrown. The handler can then call invoke-restart in order to invoke one of the restarts provided by the library. As part of the library, there is one handler per restart provided, since those are the most common kinds of handlers. Here is what happens when each of the handlers are used when running the library on the two test files:

(handler-bind ((error #'skip-entry))
  (read-files files*))

=> ((5 10 15 20 25) (5 10 15 20 30 40))

(handler-bind ((error #'skip-file))
  (read-files files*))

=> ((5 10 15 20 25))

(handler-bind ((error (use-value-handler 0)))
  (read-files files*))

=> ((5 10 15 20 25) (5 10 15 20 0 30 40))

The really cool thing about restarts is what happens when the user doesn’t handle the error. When this happens they will enter the Slime Debugger. From there they will be given a list of the restarts that are available to them and they will be able to invoke them as if the error had been handled in the first place! Here is what happens when a user doesn’t handle the error, and then invokes the skip-entry restart on the fly:

 

ezgif.com-optimize (3)

 

What’s really cool about this is that this “interactive restarting” can use it to implement breakpoints! As I said in Part 1, Common Lisp provides breakpoints as a function “break” instead of as a feature of the editor. Here is code that could be used to implement break:

(defun break (&optional (format-control "Break")
              &rest format-arguments)
   (with-simple-restart (continue "Return from BREAK.")
     (let ((*debugger-hook* nil))
       (invoke-debugger
         (make-condition 'simple-condition
           :format-control   format-control
           :format-arguments format-arguments))))
   nil)

The code for break works by signalling an error while providing a “continue” restart. This means that as soon as the function break is called, you will enter the debugger with a restart available which will continue normal execution. Exactly what a breakpoint actually is.

Restarts are another fantastic part of debugging Common Lisp. They give you better control over what happens when an error occurs. And, if your code doesn’t handle the error itself, you can still recover the process by using an interactive restart.

  1. A dynamic variable is basically a global variable that can be shadowed. When a dynamic variable is shadowed, any reference to it refers to the new binding. Once execution leaves the form that shadowed the dynamic variable, the dynamic variable reverts back to its previous binding.

5 thoughts on “Debugging Lisp Part 4: Restarts

  1. Of all the tutorials/articles on Common Lisp conditions/restarts system this one IMHO is pretty easy to understand.

    Thanks for explaining it from the point-of-view of throw/catch, makes one see the awesomeness of it.

    Looking forward to read more of your articles.

    Thank you for taking time to write them.

  2. I just purchased two other Lisp books through leanpub. I think the materials in this series of blog posts would make a nice short epub (except that the animated images wouldn’t work as nicely). Anyhow… the point is… give me some way to pay for this. :)

  3. As someone who is currently using ruby everyday and learning clojure, this(and your other tutorials) makes me wish it was easier to make a business case to use common lisp in my industry (web development).

    I suppose I can still do common lisp in my freetime…

    1. (I use both CL and Clojure in web development. I prefer CL)

      here is a business case: 30 years from now the Common Lisp code will still run as long as you use an ANSI compliant implementation. Common Lisp as a standard is frozen and won’t be changing again … if ever, which is a good thing.

      In terms of web development using Common Lisp – it’s tools and libraries are on par or more mature then Clojure tools or libraries. CL-WHO generates dynamic html5 (or any other html), PARENSCRIPT generates javascript, as for web frameworks you have CAVEMAN2 and others. DRAKMA handles http requests and such, CXML parses xml. If you need python wsgi or ruby rack – Clack provides that functionality for Common Lisp. If you need fastCGI there is a Common lisp binding for that. And of course you can use any javascript libraries including AngularJS(or Angularjs2) or Jquery with PARENSCRIPT and CL-WHO.

  4. These tutorials are fantastic! While there are great Common Lisp resources and documentation over the web, most of them are a bit harsh on beginners, not making the examples clear or concrete enough. These short examples and screencasts convey much more _useful_ information in such a small space.

    And the page looks good too. It’s unusual for Common Lisp tutorials to be so slick. Good job!

Leave a Reply

Your email address will not be published. Required fields are marked *