A GOOPS method is like a Scheme procedure except that it is specialized for a particular set of argument classes, and will only be used when the actual arguments in a call match the classes in the method definition.
(define-method (+ (x <string>) (y <string>)) (string-append x y)) (+ "abc" "de") ⇒ "abcde"
A method is not formally associated with any single class (as it is in many other object oriented languages), because a method can be specialized for a combination of several classes. If you’ve studied object orientation in non-Lispy languages, you may remember discussions such as whether a method to stretch a graphical image around a surface should be a method of the image class, with a surface as a parameter, or a method of the surface class, with an image as a parameter. In GOOPS you’d just write
(define-method (stretch (im <image>) (sf <surface>)) ...)
and the question of which class the method is more associated with does not need answering.
There can simultaneously be several methods with the same name but different sets of specializing argument classes; for example:
(define-method (+ (x <string>) (y <string)) ...) (define-method (+ (x <matrix>) (y <matrix>)) ...) (define-method (+ (f <fish>) (b <bicycle>)) ...) (define-method (+ (a <foo>) (b <bar>) (c <baz>)) ...)
A generic function is a container for the set of such methods that a program intends to use.
If you look at a program’s source code, and see (+ x y)
somewhere
in it, conceptually what is happening is that the program at that point
calls a generic function (in this case, the generic function bound to
the identifier +
). When that happens, Guile works out which of
the generic function’s methods is the most appropriate for the arguments
that the function is being called with; then it evaluates the method’s
code with the arguments as formal parameters. This happens every time
that a generic function call is evaluated — it isn’t assumed that a
given source code call will end up invoking the same method every time.
Defining an identifier as a generic function is done with the
define-generic
macro. Definition of a new method is done with
the define-method
macro. Note that define-method
automatically does a define-generic
if the identifier concerned
is not already a generic function, so often an explicit
define-generic
call is not needed.
Create a generic function with name symbol and bind it to the variable symbol. If symbol was previously bound to a Scheme procedure (or procedure-with-setter), the old procedure (and setter) is incorporated into the new generic function as its default procedure (and setter). Any other previous value, including an existing generic function, is discarded and replaced by a new, empty generic function.
Define a method for the generic function or accessor generic with parameters parameters and body body ....
generic is a generic function. If generic is a variable
which is not yet bound to a generic function object, the expansion of
define-method
will include a call to define-generic
. If
generic is (setter generic-with-setter)
, where
generic-with-setter is a variable which is not yet bound to a
generic-with-setter object, the expansion will include a call to
define-accessor
.
Each parameter must be either a symbol or a two-element list
(symbol class)
. The symbols refer to variables in
the body forms that will be bound to the parameters supplied by the
caller when calling this method. The classes, if present,
specify the possible combinations of parameters to which this method
can be applied.
body … are the bodies of the method definition.
define-method
expressions look a little like Scheme procedure
definitions of the form
(define (name formals ...) . body)
The important difference is that each formal parameter, apart from the
possible “rest” argument, can be qualified by a class name:
formal
becomes (formal class)
. The
meaning of this qualification is that the method being defined
will only be applicable in a particular generic function invocation if
the corresponding argument is an instance of class
(or one of
its subclasses). If more than one of the formal parameters is qualified
in this way, then the method will only be applicable if each of the
corresponding arguments is an instance of its respective qualifying class.
Note that unqualified formal parameters act as though they are qualified
by the class <top>
, which GOOPS uses to mean the superclass of
all valid Scheme types, including both primitive types and GOOPS classes.
For example, if a generic function method is defined with
parameters (s1 <square>)
and (n <number>)
, that
method is only applicable to invocations of its generic function that
have two parameters where the first parameter is an instance of the
<square>
class and the second parameter is a number.