The most complicated aspect of generic methods is qualifiers. In addition to the usual stack of main methods (called “primary” methods), EIEIO (following CLOS) provides for supplementary stacks that run before, after, or around the primary stack. You do this with the :before, :after or :around qualifier tags, inserted after the method name. Methods with no qualifier tags are assumed to be :primary methods.

When a method is called, the “first half” of the :around methods are run first. Then all the :before methods run. Then the :primary methods. Then the :after methods. Then the “second half” of the :around methods.

The :around and :primary methods get to choose where in their body the next method is called, by placing cl-call-next-method where they want it.

Clear as mud? Here’s what it looks like:

( defclass parent () nil) ( defclass child (parent) nil) ( cl-defmethod foo :around ((obj child)) (message "one" ) (cl-call-next-method) (message "eleven" )) ( cl-defmethod foo :around ((obj parent)) (message "two" ) (cl-call-next-method) (message "ten" )) ( cl-defmethod foo :before ((obj child)) (message "three" )) ( cl-defmethod foo :before ((obj parent)) (message "four" )) ( cl-defmethod foo ((obj child)) (message "five" ) (cl-call-next-method) (message "seven" )) ( cl-defmethod foo ((obj parent)) (message "six" )) ( cl-defmethod foo :after ((obj child)) (message "nine" )) ( cl-defmethod foo :after ((obj parent)) (message "eight" )) (foo (make-instance 'child))

Overuse of method qualifiers is a great way to get yourself turned around quick. A few things to note:

The :before and :after methods cannot use cl-call-next-method . This means they are always run, in order from most-specific to least-specific, independently of the rest of the code. Because of this, :before and :after methods can’t interact with other methods at all. This means they’re only good for general set-up and tear-down, though of course, if a :before method signals an error, nothing after it runs (which is one of the main uses of :before methods). And if a :primary method signals an error, none of the :after methods run. The methods which are allowed to use cl-call-next-method (the :around and :primary methods), can use it to fundamentally alter the behavior of the composed method call. Callers can replace the arguments to the next method call, and/or intercept the return value and do something with it. If cl-call-next-method is called with no arguments, it receives the same arguments as the caller did. If the caller wants to replace any arguments, all arguments must be explicitly passed again. You can see this happening in the ebdb-record-field-slot-query definitions above. In the :around methods, cl-call-next-method will move down the :around stack. At the bottom of the :around stack, the next call will run the :before, :primary, and :after stacks, after which control is passed back up the :around stack. The :around methods should always contain a call to cl-call-next-method , that’s their whole point. The :primary methods can call cl-call-next-method to run the next :primary method, but they don’t have to. If they don’t, they fully override all less-specific methods.

In practice, I found having more than one :around method to be fairly baffling. It simply got too complicated to keep track of. Later I decided not to use :around methods at all, and to reserve them for user customization (that’s not entirely true, but I didn’t use them much).

Did I mention the :extra methods? No, I didn’t.

There’s one more qualifier, called :extra. This is a way of piling multiple methods onto the same set of specializers (otherwise each method would clobber the last). Each one carries the :extra tag, plus a string label for identification. They are run just before the :primary methods, and calling cl-call-next-method within them calls down through the :extra stack, to the :primary methods. :extra methods are run in the order they were defined.

This turned out to be perfect for implementing internationalization for EBDB.

BBDB – and vanilla EBDB – are mostly unaware of different countries and scripts; they have a mild North American bias. I wanted to set things up so that developers could write their own country-specific customization libraries, which users could load as they liked, to extend EBDB’s basic behavior. If we know the country code of a phone number, for example, we should be able to display the number according to the standards of that country.

So we have the ebdb-i18n library. This file does nothing on its own, it only provides the hooks for country-specific libraries. As EBDB is a work in progress, I’ve so far only written support for my own needs, which are China-centric.

It always bothered me that Chinese names were displayed in BBDB as (given name)(space)(surname), ie “锦涛 胡”, rather than the proper order of (surname)(given name): “胡锦涛”. If you gave records a name-format field, you could get “胡, 锦涛”, which was better, but still not right. (Other people have also addressed this problem.)

Loading ebdb-i18n.el will load (among other things) the following :extra method for the display of name fields:

( cl-defmethod ebdb-string :extra "i18n" ((name ebdb-field-name-complex)) ( let* ((str (cl-call-next-method name)) (script (aref char-script-table (aref str 0)))) ( unless (memq script ebdb-i18n-ignorable-scripts) ( condition-case nil ( setq str (ebdb-string-i18n name script)) (cl-no-applicable-method nil))) str))

This method shadows the primary method. The first thing it does is to call that :primary method, using cl-call-next-method , so it can examine the results. It looks at the first character of the name, looks up the script the character is written in, and attempts to call ebdb-string-i18n with the name field and the script symbol as arguments. If no country-specific libraries have been loaded, there will be no method that can catch these particular arguments, in which case the original string is returned.

Loading ebdb-chn.el defines this method:

( cl-defmethod ebdb-string-i18n ((field ebdb-field-name-complex) (_script (eql han))) ( with-slots (surname given-names) field (format "%s%s" surname (car given-names))))

Chinese characters register as the ’han script. So we specialize on the symbol ’han (using (_script (eql han)) ), and if it matches, format the name the way it’s usually formatted in China.

If :extra methods didn’t exist, the internationalized ebdb-string method would clobber the primary method completely. We’d have to replicate that primary method here, or continually check some variable and funcall different functions, or even subclass the name field class with a new “internationalized” version. None of those options are as elegant as the :extra trick.

The ebdb-chn.el library defines many other internationalized methods, notably some that memoize Chinese characters as romanized “pinyin”, so you can search for contacts with Chinese names without having to switch input methods. Very nice.

Other internationalized methods allow for dispatch on the country code of phone numbers, or the symbol names of countries (as per ISO 3166-1 alpha 3).