Playing around with EIEIO (a.k.a. CEDET internals, part 1)

We're on the brink of the Emacs 23.1 release, so it's about time to
look at the new stuff 23.2 will have… One major thing that is
planned is the merging of CEDET into Emacs. While CEDET in itself is
great stuff already, it will also include some general new packages it
heavily depends on and which will bring some interesting new concepts
to programming in Emacs Lisp. Also, understanding these packages is
important if you'd like to start hacking CEDET yourself.

CEDET extensively uses two packages which I guess most people are
unfamiliar with: mode-local (which is in the 'common' directory, written by David Ponce) and
EIEIO (written by Eric Ludlam). The first one can be roughly described as "buffer-local
variables, but for major-modes instead of buffers". That means, you
can generally set variables depending on the major mode of a buffer -
CEDET uses these mode-local variables to setup the language parsers
for the different buffers.

In this post however, I'd like to take a look at the other package,
EIEIO, which is way bigger and much more intricate than
mode-local. It's a framework to write object-oriented code in Emacs
Lisp, trying to remain as close to CLOS as it is possible in Emacs
Lisp. For those who have never heard of CLOS: it stands for "Common
Lisp Object System" and is the standard for doing OOP in Common
Lisp. You probably know OOP from C++ or Java, but you'll find that
CLOS is pretty different from those in several aspects. While there
are classes and methods (of course), most of the other stuff is
completely different, and often times it is best to just forget what
you learned in C++ or Java. There are several nice tutorials for CLOS,
and I give some links at the end of this post. Most of that stuff you
read there can be applied to EIEIO, but there are some differences,
which are described in the EIEIO manual (section "CLOS
compatibility"). For a quick overview of CLOS, its Wikipedia article
is a good starting point.

EIEIO is a part of CEDET, so if you want to try out the following
examples, just download and compile it. If you just want to use EIEIO,
you don't have to load the entire CEDET suite in your init file;
just put (require 'eieio) in your .emacs.

So, 'defclass' obviously defines a class, and it has the following
syntax:

(defclass name superclass slots &rest options-and-doc)

If you're familiar with OOP in general, you probably figured out
yourself that 'superclass' is CLOS-speak for the class this one
inherits from (the "parent"), and 'slots' are like 'member variables'
in C++/Java - in the above example we have 'name', 'birthday', and
'phone'. You also see that you can define additional attributes for
these slots, like the type, default values and documentation.

If you evaluate this example and do "C-h f record RET", you'll get the
following:

You see that 'defclass' automatically created a constructor function
'record' which creates an object of the class. It also created a
proper documentation string, describing the slots of the class with
their types and default values (if specified).

Each instance is given a name as the first argument, so different
instances can be easily distinguished during debugging. The further
arguments are the slots and their values (by the way: in CLOS you
create an instance using 'make-instance', and this is also possible in
EIEIO).

You can check if 'rec' is really of class 'record' using the
automatically generated function 'record-p'. For retrieving and
setting its slot values, use 'oref' and 'oset':

(defmethod call-record ((rec record) &optional scriptname)
"Dial the phone for the record REC.
Execute the program SCRIPTNAME as to dial the phone."
(message "Dialing the phone for %s" (oref rec name))
;; to be implemented...
)

You see that there is no notation like "OBJECT.method", since in CLOS methods do not really "belong" to classes. Instead,
the code which is executed is derived from the arguments given to the function. Therefore, you call such a method like you would call any
other function:

(call-record rec)
=> "Dialing the phone for Random Sample"

If you have several 'call-record' methods for different classes, it
will know by its first argument which one to call. Let's take a look
at the doc-string of 'call-record':

call-record is a generic function with only one primary method.
Documentation:
Dial the phone for the record REC.
Execute the program SCRIPTNAME as to dial the phone.
Implementations:
`record' :PRIMARY (rec &optional scriptname)
Dial the phone for the record REC.
Execute the program SCRIPTNAME as to dial the phone.

You see that it lists all implementation of 'call-record', i.e. for
all classes it is defined for. If you'd create another class with a
'call-record' method, it would also be listed under "Implementations"
(try it out!). But what does "with only primary methods" mean?

Looking at the doc-string of 'abroad-record', you'll see that it
mentions that it inherits from 'record', and you'll also see that it
inherited all slots from 'record', but now also contains the slot
'country'.

This function just prepends the country code for Italy if
necessary. If you think all this 'oset' and 'oref' stuff is tedious -
you are right. But you can define so called "accessor" functions: in
the slot definition you can write

:accessor get-phone

and the you can just use (get-phone rec) to obtain the phone number.

Now, you see that there is this argument ":before", and this is where
it gets interesting. It means that if you do

it will first call the 'call-record' from 'abroad-record', and then
it automatically calls 'call-record' from 'record'. Therefore, the
Message buffer shows:

Prepending country code to phone number.
Dialing the phone for Good Friend

You'll also see that the 'phone' slot now also has the country-code
"0043" before the actual number. (BTW, I know this example doesn't
make much sense and you would usually solve this problem
differently… it's just an example, after all.)

Generally speaking, if more than one class defines a method with the
same name (which is called a generic function), the applicable
methods are combined into one single effective method. How these
single methods are combined is where it gets really interesting, and
in CLOS you can even define this yourself. However, the default is the
so called standard method combination, and this is also what EIEIO
does.

Next to ":before", you might guess that there's also ":after". If you
omit this specification, it is ":primary" by default. These primary methods form the the main body of the effective
method; in our case, it's the method which does the actual
calling. This also answers the above question what EIEIO means with
"generic function with only one primary method. A generic function can have several primary methods, and
you can manually invoke the next most
specific method via

(call-next-method)

All this is just scratching on the surface on how methods are combined
to the effective method. I just mention that there's also ":around"
and ":static", and that you can easily do multiple inheritance, which
is when method invocation gets really powerful (and complicated). And
if you were wondering: yes, there's also a standard method
'initialize-instance' which is similar to a constructor in C++. If
you'd like to know more, take a look at the following tutorials:

At the end, let's take a short look how CEDET uses EIEIO. The part of
CEDET which makes extensive use of EIEIO is EDE, the package for
managing projects. For an example, take a look at the class
'ede-compiler', which defines a general compiler, and classes like
'ede-object-compiler' inherit from this class. They include other
objects in their slots, for example 'ede-sourcecode', which defines
which kind of sourcecode the compiler can compile. Therefore, if you'd
like to include new compilers/linkers into EDE, you simply define new
sourcetypes, compiler and linker classes and fill the slots
appropriately.

Another thing which makes EIEIO very powerful is that you can easily
build complex customization buffers for manually editing object
slots. For an example, simply evaluate

(eieio-customize-object rec)

I should also mention that EIEIO is not the only object system for
Emacs Lisp. For example, the shimbun library from emacs-w3m uses
'luna', which is part of FLIM and much smaller than EIEIO, but also
with less features (e.g. multiple inheritance is not really working
with it).