Generic Functions and Methods

A generic function is a collection of methods. The particular method to execute is chosen depending on the classes of the arguments to the generic function. For example, consider the case of arithmetic functions: the operation to add together two integers is different from that for adding two floats, or two rationals, or two polynomials. Yet, the operation is nominally, +, the same in each case. Since + is already defined, let us define a generic function called plus to integrate the <ratio> class defined earlier: (defgeneric plus (a b) method: (((a ) (b )) (+ a b)) method: (((a ) (b )) (let* ((d (* (den a) (den b))) (n (+ (* (num a) (den b)) (* (num b) (den a))))) (ratio n d))))

Defining a generic function

The syntax for defining a generic function is:

(defgeneric gf-name gf-args gf-options)

Where the terms in italics have the following properties:

gf-name
An identifier which names the generic function. The value of the identifier is the new generic function. In our example, this is plus.
gf-args
A list of the arguments to the generic function. In our example, this is (a b). In fact, the names are irrelevant, what is important is the number of arguments. You can also define generic functions that take variable numbers of arguments in just the same way as ordinary functions. For example: (defgeneric foo (a . b)) defines a generic function (with no methods) taking at least one argument, with the second always being of class <list>.
gf-options
There is just of these in EuLisp at level-0, namely the method option, which allows you to define a method to attach to the generic function. In our example we have two: method: (((a ) (b )) (+ a b)) method: (((a ) (b )) (let* ((d (* (den a) (den b))) (n (+ (* (num a) (den b)) (* (num b) (den a))))) (ratio n d))) where the first calls the system function + on any argument which is an instance of <number> (note: when we write instance of, this means the object may be an instance of the class referred to (a direct instance) or an instance of any of its subclasses (an indirect instance)). The second method takes two instances of <ratio> and adds them, returning a new rational number.

There is, in fact, a single expression following each method: keyword. The first part of it is the specialized parameter list and the rest is the body of the method. The specialized parameter list is very similar to the formal parameter list of a function except that instead of identifiers you may write (id class). This indicates that this parameter must be an instance of class for this method to be applicable. Hence, in the first case, both a and b must be instances of <number>, while in the second they must both be instances of <ratio>. However, if you just write id, then it is assumed it should be an instance of <object>. Now we can use plus:

user> (setq x (ratio 3 4)) # user> (setq y (ratio 5 2)) # user> (plus x y) #

Methods

Although the method gf-option allows the definition of a method at the same time as the generic function is created, this may not always be convenient. Indeed, it is much more common to define methods later, even in different modules from the one containing the generic function definition. Consequently, there is an alternative means for definining methods.

Defining Methods

The syntax for a method definition is:

(defmethod gf-name arg-list body)

where the terms in italics have the following meaning:

gf-name
An identifier naming an existing generic function. This is the generic function to which the method will be attached.
arg-list
As described above under Defining Generic Functions, this like an ordinary function argument list, except that some elements indicate specialization by means of a list of the formal parameter name followed by the required class.
body
A sequence of expression, just as for any other function. The result of calling the generic function is the result of the last expression executed in the body.
We could decide that we would like to keep rational numbers in reduced form, by doing a gcd computation each time. Hence, we redefine the plus method for two instances of <ratio> as follows: (defmethod plus ((a ) (b )) (let* ((d (* (den a) (den b))) (n (+ (* (num a) (den b)) (* (num b) (den a)))) (g (gcd n d))) (ratio (/ n g) (/ d g)))) Consequently user> (plus x y) # However, more important than redefining existing behaviour is extending existing behaviour. The output representation of rational numbers, although informative, is not very pretty. We can add a new method to generic-write to change that: (defmethod generic-write ((x ) s) (format s "~s/~s" (num x) (den x))) and now user> (plus x y) 13/4 There is one more important thing to point out: what happens when there is no suitable method. Consider the case of calling plus with an integer and a rational: user> (plus 3 x) Error---calling default handler: Condition class is # message: "no applicable methods" value: (plus 3 #) Debug loop. Type help: for help Broken at # DEBUG> Clearly, this deficiency can be addressed by defining two new methods.
Julian Padget, jap@maths.bath.ac.uk, this version January 11, 1995