Skip links

Common Lisp Metaobject Protocol: Classes are Just Objects!

In today’s popular languages such as C++, Java, Golang, Rust, Python, etc., classes are fixed constructs defined by the language. They have a definite syntax that can’t be changed while programming. We are all used to this of course. But what makes Common Lisp stand out is that in that language, classes, slots, methods, and generic functions are themselves objects – and you can customize how they behave!

Although many languages support various degrees of Metaprogramming, Lisp’s Metaobject Protocol (MOP) is quite involved, giving programmers tremendous flexibility in constructing a well-defined program. In today’s article, I wish to cover just  the basics of MOP.  Readers interested in learning the subtleties can go through [1].

If you haven’t heard of Common Lisp before, you may want to go through my earlier article “Why Learn Lisp?”

OK, let us start with a simple example.

A Simple Class
A Simple Class

The above is a class called dog. It has two fields name and breed.

What we are interested in is to find out the class (i.e., Metaclass) of this class. Normally, in programming languages, we are used to the fact that objects are associated with classes. In Common Lisp, we have classes for classes too!

Finding the Class of a Class
Finding the Class of a Class

It turns out that by default, all classes are instances of standard-class class. What about the standard-class itself? Quite interestingly, that is an instance of itself!

Let us now consider another example. We want to keep track of how many instances of a class exist at any point in time. Here is the example.

Class to Count Instances
Class to Count Instances

Here, counted-class is the metaclass. When an object of that type is created, it automatically increments the instance-count field. The classes tracked-animal and cat enable instance counting by declaring counted-class explicitly as the metaclass (and not by deriving from it).

Let us now create instances of these two classes and check the count. 

Checking Instance Count
Checking Instance Count

The count matches the number of instances created. 

While this can be implemented in C++ (and other OOP languages) through inheritance, complexity arises if your class is already deriving from another class. In some languages multiple inheritance is not supported, and even in C++ many argue against the inherent complexity of MI. 

However, in Common Lisp, it is possible to change the metaclass dynamically, at runtime!

What if I have a library class (I don’t have access to its source), whose instances I want to count? For instance, assume that customer is a class in a library whose source I don’t have. How will you handle this situation in C++ or other OOP languages?

It is quite easy in Common Lisp. I can change its metaclass at run time:

(change-class (find-class ‘customer) ‘counted-class)

Isn’t that interesting? That is the power of MOP.

Our last example demonstrates MOP’s unique power: intercepting slot reads and writes without modifying the class. Let us create a metaclass that automatically logs every slot read and write.

Metaclass to Trace Slots
Metaclass to Trace Slots

Here’s where the magic happens. slot-value-using-class is called whenever a slot is read; (setf slot-value-using-class) is called whenever a slot is written. We add :around methods to both:

Intercepting Slot Reads
Intercepting Slot Reads

The above code intercepts slot reads. Here is the code to intercept slot writes:

Intercepting Slot Writes
Intercepting Slot Writes

Let us define a class whose slots we want to trace:

Example Class
Example Class

As you can see, we have specified the metaclass as traced-class. Let us see what happens when we create an instance and access the slots:

Using bank-account Class
Using bank-account Class

The reads and writes are automatically logged. Nice.

What if you wish to stop logging at some point without changing the class definition? Quite simple:

(change-class (find-class ‘bank-account) ‘standard-class)

That is the power of MOP! Of course, you can do a lot more with MOP, but that is for another article!

I tested the code in Allegro Common Lisp Enterprise Edition version 11. You can download the source here.

Wish you a Happy and Prosperous New Year!

Further Reading

1) Gregor Kiczales, Jim des Rivieres, and Daniel G.Bobrow, “The Art of the Metaobject Protocol”, The MIT Press, 1999.

Leave a comment