The purpose of object oriented design is to reduce the cost of change.

One of the ideas behind type systems is that a language compiler can make some optimizations if it knows about the type of objects it will be dealing with in runtime. Common Lisp allows, but does not require, a programmer to declare types ahead of time. Ruby doesn’t care about types at all, if an object has a method, it can be called. This is called “Duck typing” after the idea that, “if it walks like a duck, swims like a duck, and quacks like a duck” then we can call it a duck.

However, just because Ruby pretty much ignores types, programmers might feel tempted, even subconsciously, to write type-sensitive code. Chapter 5 of POODR gives us a couple of examples in Ruby, and discusses how to rewrite these. I’ll review and translate these examples in Common Lisp.

The first example shows a Trip class with a prepare method that depends on an argument of type Mechanic to work. Translating the examples from page 87 into Common Lisp shows a different side to the problem:

(defclass trip ()
  ((bicycles  :reader :bicycles)
   (customers :reader :customers)
   (vehicle   :reader :vehicle)))

(defclass mechanic () ())

(defmethod prepare ((trip trip) (mechanic mechanic))
  (prepare-bicycles mechanic (bicycles trip)))

(defmethod prepare-bicycle ((mechanic mechanic) bicycle)
  ;; do something
  T)

(defmethod prepare-bicycles ((mechanic mechanic) &rest bicycles)
  (map 'list (lambda (bicycle) (prepare-bicycle mechanic bicycle)) 
       bicycles))

The methods PREPARE-BICYCLE and PREPARE-BICYCLES more-or-less follow from earlier principles of specializing on their tasks, but as interfaces, they are brittle and specific. We want to change this code, to generalize or add other objects and methods, so we’ll have to change the interface.

POODR discusses how a programmer might feel tempted to build their own dispatching code to try to solve this problem. Translated to Common Lisp it looks like this:

(defclass trip-coordinator () ())

(defmethod buy-food ((trip trip) (tc trip-coordinator)))

(defclass driver () ())

(defmethod gas-up ((trip trip) (driver driver)))

(defmethod fill-water-tank ((trip trip) (driver driver)))

(defmethod prepare ((trip trip) &rest preparers)
  (loop
     for preparer in preparers
     do (etypecase preparer
          (mechanic (apply #'prepare-bicycles preparer (bicycles trip)))
          (trip-coordinator (apply #'buy-food preparer (customers trip)))
          (driver (progn
                    (apply #'gas-up preparer (vehicle trip))
                    (apply #'fill-water-tank preparer (vehicle trip)))))))

Although it seems like the PREPARE method is nicely generalized and easy to extend, it doesn’t reflect the relationship of the tasks. This is where duck typing is necessary.

In in Ruby, and in this chapter of POODER, we could give each of TripCoordinator, Mechanic, and Driver classes a prepare_trip method to specialize on. There’s no need to be concerned about their type, only that they have the expected behavior.

But in CLOS, methods have their own type dispatch. If used properly, it has the effect that defining a specialized method for each class does in the Ruby example. Something like this:

(defmethod prepare ((trip trip) &rest preparers)
  (loop
     for preparer in preparers
     do (prepare-trip trip preparer)))

(defmethod prepare-trip ((trip trip) (mechanic mechanic))
  (loop
     for bicycle in (bicycles trip)
     do (prepare-bicycle bicycle)))

(defmethod prepare-trip ((trip trip) (tc trip-coordinator))
  (buy-food trip tc))

(defmethod prepare-trip ((trip trip) (driver driver))
  (let ((vehicle (vehicle trip)))
    (gas-up vehicle)
    (fill-water-tank vehicle)))

This isn’t exactly the type insensitivity as in Ruby, the methods do depend on the type of object they recieve, but in Common Lisp we’re not building a hierarchy of objects, we’re building a network of them. The PREPARE-TRIP method doesn’t “belong” to the objects of MECHANIC, TRIP-COORDINATOR, of DRIVER, it belongs to it’s class of GENERIC-FUNCTION. Generic function objects are for creating behaviors for different objects, classes, or any combination thereof. It’s not duck typing, but it’s just as flexible.

This is discussed further in Chapter 16 of Peter Seibel’s Practical Common Lisp and in Sonja Keene’s excellent Object Oriented Programming in Common Lisp.

When should we write our own type dispatch? Metz asks us to think about this when considering using Ruby’s kind_of? and responds_to? methods. She draws on an example from Rails (since rewritten), and shows a method with some type conditions. It’s not easily translatable without some distracting elements so consider the following code I used in my Wizard’s Castle adaptation:

(defun value-of-event (event &optional value-ref)
  "Get most specific information about event"
  (assert (event-p event))
  (etypecase value-ref
    (null        (first (last event)))
    (symbol      (rest (member value-ref event)))
    ((integer 0) (last event value-ref))))

The first assertion is a kind of early failure type checking that Metz is leading us to be skeptical of. This function will only ever work on objects that EVENTP approves of. This code cannot be reused. Events objects will have a kind of brittleness to them.

However the ETYPECASE form is not necessarily so bad. Metz makes the case that the interface to the built in types, here being the NIL, SYMBOL, and INTEGER, are much more stable than other classes that I might make. With this stability, it makes sense that I could use them like this to decide something (in this case, how to retrieve data from my event objects implemented as a Lisp list).

Should the time come to refactor the events system, I’ll consider making the events a class, and define methods for it. I’ll consider carefully those methods, and how they can work with the other objects in the Wizard’s Castle game.

next

prev