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.