Why CLOS is the Coolest Object System Ever!
What's this? A blog post about OOP from a guy who obviously hates OOP? What's going on? D:
Let me tell you what's going on; cool tech so old it mostly falls under the radar.
Common Lisp Object System, as it's called, is about as old as object systems get, but that does not hurt it at all.
As with anything else in Common Lisp (CL), the object system is handled a little bit differently from most other languages. As an example, it's much more reminiscent of C's way of programming with structs and functions, in that it doesn't support the familiar dot notation of popular languages like Python, Ruby, C++, Java, or C#. But believe it or not, that actually works to its advantage.
A bit of background first in reading Lisp syntax if you're not familiar. A function call in Ruby/Python/C/etc goes like this:
foo(); // without arguments foo(a, b, c); // with arguments
In CL, or any other Lisp for that matter it's like this:
(foo) ; without arguments (foo a b c) ; with arguments
You can basically think of it as the parentheses going around the function name rather than at the end.
Cool Thing #1
CL supports multiple inheritance!
I can already hear the C++ programmers starting to cry in a corner having flashbacks of the diamond problem and all the nightmares that entails.
But fear not! CL gets around this in a clever little way. The order in which the classes are inherited defines the precedence of the inherited fields and methods.
So suppose we write this (... marks irrelevant code):
(defclass C (A B) ...) (defclass D (B A) ...)
Where both A and B define a field called data. C will inherit A's data field, while D will instead inherit B's.
Okay, granted multiple inheritance isn't all that impressive; Scala has it too via traits, and it works much the same way. But Scala is much newer than Common Lisp, so it wouldn't be unfair to suspect Scala got this mechanism from CL.
Cool Thing #2
CL doesn't use message passing, it uses generic functions!
"Okay, why does that make a difference?" I hear you say. It makes a tremendous difference actually, most of the awesome CLOS features are a result of this one decision.
Lisp used to have a Smalltalk-like method system working with message passing, but the issue with this is that it didn't integrate well with the way CL handles higher order functions. So to get around this, the creators of CLOS tried to make methods as function-like as possible so the user wouldn't have to worry about the implementational differences between the two and just use them.
To illustrate this important difference let me show you an example:
Say you have a method foo which you need to apply across every element in a list called lst. Using a send-like syntax like the one used in Racket, you'd need to do something like this:
(mapcar #'(lambda (x) (send x foo)) lst)
And now using generic functions instead of messages:
(mapcar #'foo lst)
Notation identical to as if foo was just a regular function.
But this isn't the only benefit of using generic functions over messages!
Cool Thing #2.1
Methods in CL aren't defined as part of a class!
You heard me right! Just like how functions aren't defined as part of structs/records in C and Haskell, methods aren't defined as part of your class.
What this means is basically that you can define a method to work with any existing class if you so desire. Users of C# and Objective-C are already familiar with this sort of concept, it's CL's equivalent of extension methods or category methods, except it goes a little bit beyond that, in that you can essentially put the method anywhere you want and start using it right away if you want to. Which means you don't need to define dedicated files just for extension methods, or a new file for each category just so you can extend the functionality of an existing class.
(defgeneric foo (a)) (defmethod foo ((a bar)) ...) (defmethod foo ((a number)) ...) (defmethod foo ((a string)) ...)
The example above defines a generic function foo which then implements three different overloaded methods, one acting on the bar class, and the other two acting on the built-in classes number and string no special hocus-pocus required.
This leads me to
Cool thing #2.2
Methods support dynamic multiple dispatching out of the box!
That's right, wave good bye to the annoying visitor pattern that plagues single dispatching. Due to the fact that methods aren't tied to any specific class right off the bat, and types are checked during runtime.
A classical example goes as follows (in pseudo-java/c#):
class A {} // implicitly extends Object void printObj(Object a) { print("'a' was of type 'Object'"); } void printObj(A a) { print("'a' was of type 'A'") } Object a = new A(); printObj(a);
Try to guess what text is going to be printed out... It's "'a' was of type 'Object'".
Despite the fact that a is an instance of A, it's still treated as an Object. This is fine for a lot of cases, but often if you have a lot of objects with a common super type that need to be dispatched on their concrete type, you'll need to whip out the visitor pattern, that checks the concrete type during runtime.
CL lets you extend this dispatching to as many arguments within a method as you please, essentially letting you be as specific as you please.
(defgeneric collide (obj1 obj2)) (defmethod collide (a b) (error "collision not defined for these objects")) (defmethod collide ((s ship) (a asteroid)) ...) (defmethod collide ((s1 ship) (s2 ship)) ...) (defmethod collide ((a asteroid) (s ship)) ...) (defmethod collide ((a1 asteroid) (a2 asteroid)) ...)
The example above gives a series of methods for a supposed space game, where objects can collide with each other, among these methods is one that takes any object at all and throws an error. Under a regular single dispatching system, if you had a list of random ships and asteroids, you'd get nothing but errors, since they'd all just be unspecific objects; but in a multiple dispatching system, all of these methods would be called correctly, only resulting in an error if something that isn't an asteroid or space ship slipped into the list of collision objects.
Cool Thing #3
(call-next-method)
CL lets you call the next-most relevant method with the same arguments as the current method from within. Think of it as calling the base-class' method with the same arguments, but automatic, and properly dispatched.
(defgeneric print-obj (o)) (defmethod print-obj (o) (print "received an object o")) (defmethod print-obj ((s string)) (print "received a string with the message") (print s) (call-next-method))
If print-obj receives a string it'll execute its own body, and then it'll run call-next-method which calls the general print-obj which works on any object. If however, print-obj receives an object that isn't a string, it'll just print "received an object o" and do nothing else.
Cool Thing #4
Method Qualifiers!
Perhaps the coolest thing about CLOS methods, is their qualifiers.
The condensed version is that you can add :before, :around, and :after to any method, which will affect the order of evaluation.
Say you have some code you always need to run after you finish some routine, instead of explicitly calling that code at the end of every method you define, you can instead do this:
(defgeneric db-connect (db obj)) (defmethod db-connect (db obj) ;; act on the database ) (defmethod db-connect :before (db obj) ;; connect to the database ) (defmethod db-connect :after (db obj) ;; cleanup code goes here )
Because the :after method is defined it'll always run the cleaning routine after any primary method (method without qualifier) is done running, and since the :after method is defined as not taking any specific type, it'll always run regardless of what the primary method gets dispatched to.
:before works exactly like :after except it runs code before the primary method, rathe than after. In the above example, this could be used to open a database connection independently of the acting code.
:around is different. It requires the use of (call-next-method), but unlike the regular use of (call-next-method) execution returns to the :around method, so that more stuff can be done.
(defgeneric move (ship dest)) (defmethod move ((ship ship) (dest planet)) ;; do stuff related to landing on a planet here ) (defmethod move ((ship ship) (dest station)) ;; do stuff related to landing on a station here ) (defmethod move :around ((ship ship) dest) ;; calculate fuel remaining (if (>= fuel 0) ; if remaining fuel after trip is ≥ 0 (call-next-method) ; call primary method (warn "Not enough fuel, going nowhere.")) ; else warn player ;; print info about destination and fuel used )
All of this just scratches the surface of what the CLOS can do, and I won't even begin to go into the Meta-Object Protocol.
Yes, CLOS is super alien looking and it has some strange features, but those features can lead to creative and elegant solutions to problems that just wouldn't be possible in "traditional" object systems. Everything from dynamic multiple dispatching to qualifiers offer "new" and interesting extensions to the classical object model.
I can recommend reading the two chapters regarding OOP in Practical Common Lisp:
Object Reorientation: Classes
Object Reorientation: Generic Functions
















