Debugging Lisp Part 3: Redefining Classes

This is part 3 of Debugging Common Lisp. If you haven’t read either of the previous parts, you can find part 1 here, and part 2 hereYou can find part 4, which is on restarts, here.

The Common Lisp Object System (CLOS) is pretty powerful. It gives you multiple inheritance, multiple dispatch, and many different ways to extend the behavior of methods. Underneath, most implementations use the Metaobject Protocol (MOP), a way of defining CLOS in terms of itself. As part of the MOP, classes are implemented as objects with several instance variables. Among those are variables that hold the class’s name, its superclasses, and a list of the class’s own instance variables. If you don’t believe me, take the point class from the previous post:

(defclass point ()
  ((x :accessor point-x :initarg :x :initform 0)
   (y :accessor point-y :initarg :y :initform 0)))

And use the Slime Inspector to inspect the point class object, which can be obtained by calling find-class:

 

ezgif.com-optimize

 

The advantage of using the MOP is that it makes it possible to fine tune the behavior of CLOS by using ordinary object-oriented programming. A great example of this is the filtered-functions library which adds arbitrary predicate based dispatch to CLOS. But enough about the MOP.1 In this post I’m going to talk about one tiny piece of CLOS, update-instance-for-redefined-class.

Update-instance-for-redefined-class is a method which is called whenever a class is redefined (at runtime). By overriding it, you can customize what exactly happens at that point in time. For example, let’s say you are using the above point class to represent complex numbers for some sort of simulation. As part of the simulation, you have a point object saved inside of the *location* variable:

 

ezgif.com-optimize (1)

 

After profiling the simulation, you find that one of the bottlenecks is complex multiplication. Since multiplication of complex numbers is much more efficient when they are represented in polar form, you decide that you want to change the implementation of the point class from Cartesian to polar coordinates. To do that (at runtime), all you need to do is run the following code:

(defmethod update-instance-for-redefined-class :before
     ((pos point) added deleted plist &key)
  (let ((x (getf plist 'x))
        (y (getf plist 'y)))
    (setf (point-rho pos) (sqrt (+ (* x x) (* y y)))
          (point-theta pos) (atan y x))))

(defclass point ()
  ((rho :initform 0 :accessor point-rho)
   (theta :initform 0 :accessor point-theta)))

(defmethod point-x ((pos point))
  (with-slots (rho theta) pos (* rho (cos theta))))

(defmethod point-y ((pos point))
  (with-slots (rho theta) pos (* rho (sin theta))))

Basically, the code extends update-instance-for-redefined-class to calculate the values of rho and theta for the polar implementation in terms of the variables x and y from the Cartesian one. After extending update-instance-for-redefined-class the code then redefines the class, causing all of the existing instances to be changed over to the new implementation.2 Finally, two methods are defined, point-x and point-y, which preserve the interface for the point class.3 After running the code and then inspecting the contents of *location*, you should see:

 

ezgif.com-optimize (2)

 

Even though the object inside of *location* is still the same object, it is now implemented using polar coordinates! To make sure that it was converted from Cartesian to polar correctly, you decide to call point-x on the object to check that the x-coordinate is still the same:

 

ezgif.com-crop

Amazingly, all of the code continues to work even though the implementation of an entire class was completely changed. So anytime you want to change the implementation of a class that is part of a service that needs to be up 24/7 and just happens to be written in Common Lisp, remember to use update-instance-for-redefined-class.

Debugging Lisp Part 2: Inspecting

This is part 2 of Debugging Lisp. If you haven’t read part 1 on dynamic recompilation, you can find it here. For the next post in the series on redefining classes, click here.

In this post I am going to discuss another tool used for debugging Common Lisp – the Slime Inspector. The Slime inspector makes it possible to manipulate objects directly from the repl. You can do many different things with it, including clicking on objects to look at their contents and being able to copy and paste objects in order to reuse them in future function calls.1 Let’s say you have the following point class:

(defclass point ()
  ((x :accessor point-x :initarg :x :initform 0)
   (y :accessor point-y :initarg :y :initform 0)))

If you were to make an instance of the above class:

(make-instance 'point :x 10 :y 20)

You can then right click on it and click on the “inspect” option, or just use the Emacs shortcut “C-c C-v TAB” to peek inside the object:

 

ezgif.com-optimize (5)

 

This will show you the current values of all of the instance variables of the object. Not only can you look at the object’s instance variables, you can modify them as well. Note that the power comes from being able to do all of this from within the debugger at runtime.

 

ezgif.com-optimize (7)

 

To make sure that the value of that object was actually changed, you can copy and paste the point object and then call the point-x function on it.

 

ezgif.com-crop (5)

 

One more really cool tool that hooks into the Inspector is the Slime Trace Dialog. The Slime Trace Dialog is like ordinary trace, but it also allows for inspection on the objects that were passed to or returned from the traced functions. For example, let’s say you are writing a tail call optimized function, sum, that sums all of the numbers in a list.

(defun sum (xs &optional (acc 0))
  (if (null xs)
      acc
      (sum (cdr xs) (+ (car xs) acc))))

(sum '(1 2 3))
=> 6

You can toggle the use the Slime Trace Dialog to trace sum by typing the shortcut “C-c M-t” and then typing in the name of function, “sum”. After tracing it and running the code, you can press “C-c T” to enter the interactive Trace Dialog buffer. From there you can press “G” to refresh it and obtain the most recent trace.

 

ezgif.com-crop (4)

 

The trace will look like the output from ordinary trace, except it will have some addition goodies. As I said above you can inspect all of the arguments and return values. You can also hide/show branches of the trace tree in order to make it easier to find what you are looking for.

 

ezgif.com-optimize (8)

 

The Slime Trace Dialog is invaluable when you have code which is passing lots of objects around and you aren’t exactly sure what the value of each variable in each object is. You can just use the Slime Trace Dialog and have it keep track of all of the information for you.

All in all, the Slime Inspector is another amazing part of the Common Lisp debugging tool set. It comes in handy when the program crashes and you are unaware of the current state of the program. When combined with the rest of the features for debugging Common Lisp, the Slime Inspector is just incredible.

Debugging Lisp Part 1: Recompilation

This post is the start of a series on how to debug Common Lisp code, specifically with Emacs, Slime, and SBCL. If you do not understand Common Lisp, you should still be able to follow along and recognize just how powerful the facilities provided by the Common Lisp debugger are. Nathan Marz asked me to write these posts since he thought many of the tools for debugging Common Lisp were pretty cool.

For the next post in the series on inspecting, click here.

The first thing you need to do in order to get started debugging Common Lisp is to set your Lisp’s optimization qualities. Optimization qualities are basically a group of settings which allow you to specify what the compiler should optimize for. These qualities include speed, space, compilation speed, safety, and debugging. If you do not run the code below, which tells the compiler to optimize for debugging, almost none of the examples in this post will work.

CL-USER> (declaim (optimize (debug 3)))
NIL

CL-USER> (your-program)
...

With the compiler optimized for debugging, it becomes possible to do pretty much everything at runtime. This post will show you how Tom, an experienced Lisp developer would debug and patch a buggy function at runtime. Let’s say that Tom has the following code which implements the well known Fibonacci function:

(defun fib (n)
  (if (<= 0 n 1)
      (/ 1 0)
      (+ (fib (- n 1))
         (fib (- n 2)))))

There’s just one problem, the code isn’t correct! Instead of returning n in the base case, the code winds up dividing by zero. When Tom tries to calculate the tenth Fibonacci with this code, a debugger window pops up because an error was signaled.

 

ezgif.com-crop

 

Realizing that he has entered the debugger, Tom wonders what has gone wrong. In order to find the bug, Tom decides to insert a breakpoint into the function.1 In Common Lisp, breakpoints are implemented as a function called ‘break’.2 To insert his breakpoint, Tom adds a call to break at the beginning of fib. After adding the breakpoint, Tom then puts his cursor next to one of the frames and hits the ‘r’ key in order to restart it. In this case, Tom decided to restart the frame where n was three.

 

ezgif.com-optimize

 

By restarting the frame, Tom basically traveled back in time to the beginning of the frame he restarted. After restarting the frame, the debugger immediately hits the breakpoint Tom had just added. From there Tom steps through the code by hitting the ‘s’ key. He eventually realizes that the base case is implemented incorrectly and that that is why he received the error.

 

ezgif.com-optimize (2)

 

After finding the source of the problem, similar to how he had previously inserted the breakpoint, Tom patches the code. He replaces the base case with n and removes the breakpoint he had previously inserted.

 

ezgif.com-optimize (3)

 

After recompiling the code, Tom once again restarts one of the frames. Since he was previously stepping through code, the debugger starts stepping through the frame Tom decided to restart. Tom just taps the ‘0’  (zero) key in order to invoke the step-continue restart3 and continue normal execution. Because Tom restarted a frame which occurred before the bug, and now that the bug is gone, the code runs as if there had never a bug in the first place!

 

ezgif.com-crop (3)

 

Let’s recap what happened. After the code signaled an error, Tom found himself in the debugger. Tom was able to insert a breakpoint and poke around until he found the source of the problem. After finding the problem, Tom patched the code and restarted the process from a point before it had signaled an error. Because Tom had corrected the code, after he restarted the frame, it acted as if nothing had ever gone wrong!

The ability to recompile code at runtime is just one of the many incredible features provided by Common Lisp. Next time, I’m going to talk about the Slime inspector, which makes it possible to look into and modify objects from within debugger.