8.6 Methods and Generic Functions

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.

syntax: define-generic symbol

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.

syntax: define-method (generic parameter …) body …

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.