goops-generic-function


This page wants to be a tutorial on generic functions in GOOPS, the object oriented extension for Guile. All the code is tested under Guile version 1.8.

All the functions used in this page are described in the GOOPS documentation distributed with Guile.

uriel

While some knowledge of the Scheme/Guile language is required, this page is meant to be used by non-experts, too.


A quick example

The following script shows how to define two methods for the same generic function, doit:

 (define-module (test) 
   #:use-module (oop goops) 
   #:duplicates merge-generics) 
  
 (define-class <a> ()) 
 (define-class <b> ()) 
  
 (define-method (doit (o <a>)) (display 'a)(newline)) 
 (define-method (doit (o <b>)) (display 'b)(newline)) 
  
 (define a (make <a>)) 
 (define b (make <b>)) 
  
 (format #t "calling with <a>: ") 
 (doit a) 
 (format #t "calling with <b>: ") 
 (doit b) 

if we execute it the output is:

calling with <a>: a
calling with <b>: b

that is:

This allows us to write a function like:

 (define (do-something o) 
   (do-some-other-thing (doit o))) 

that can be applied to objects of both the classes <a> and <b>.

Generic functions and methods definition

Going with the GOOPS documentation: ''a generic function is a collection of methods, with rules for determining which of the methods should be applied for any given invocation of the generic function''.

So we have to make clear what is a generic function and what is a method. If the generic function is a collection there must be a way to create an empty collection; this is the define-generic syntax:

 (define-generic doit) 

it creates a new variable, bound to the doit symbol, holding the generic function's value. define-generic can be invoked only in the top level environment, but different modules can have separated generic functions with the same name.

A method definition uses the define-method syntax, and it adds a new method to a generic function (puts a new element in the collection); it looks like this:

    ------------------------------------- syntax used to define
   |                                      a new method
   |
   |              ----------------------- the name of the variable
   |             |                        holding the function is
   |             |                        'doit'
   |             |
   |             |     ------------------ an argument named 'p'
   |             |    |                   with class <a>
   |             |    |
   |             |    |        ---------- an argument named 'q'
   |             |    |       |           with class <b>
   |             |    |       |
   |             |    |       |       --- an argument named 'r'
   |             |    |       |      |    with no class specification
   v             v    v       v      v
(define-method (doit (p <a>) (q <b>) r)
  ...) 
   ^
   |
    --- here we put the function's body forms

any number of arguments can be used in the definition, with or without class specification. If the class specification is missing: GOOPS defaults it to <top>, a conventional value that matches any class.

There is no constraint on the order of arguments and a method can have variable number/optional arguments stored in a list with the dot notation:

 (define-method (doit (p <a>) . args) 
   ;; here 'args' is the list of additional arguments, possibly empty 
   ...) 

We can invoke define-method any number of times with the same function name: each time we add a new method to the collection. If a new method has arguments specification equal to the one of an already existent method, that old method is overwritten.


If we invoke define-method without having first called define-generic: a new generic function is automatically created. Beware, though, that there are situations in which this code:

 (define-method (doit (o <a>)) 
   ...)  

is not equivalent to:

 (define-generic doit) 
  
 (define-method (doit (o <a>)) 
   ...)  

more on this later when dealing with overloading of ordinary functions.

One method, one closure

When we define a method, we are actually defining a closure; the following script:

 (define-module (test) 
   #:use-module (oop goops) 
   #:duplicates merge-generics) 
  
 (define-class <a> ()) 
  
 (define data 1) 
  
 (define-method (action (o <a>)) 
   (display data) 
   (newline)) 
  
 (define o (make <a>)) 
  
 (let ((data 2)) 
   (action o)) 
  
 (define data 3) 
  
 (action o) 
 (action (make <a>)) 

outputs 1, then 3 and then 3 again: the data variable is associated to the (action (o <a>)) method, it is not associated to a specific instance and it is not associated to the generic function.


Try this script:

 (define-module (test) 
   #:use-module (oop goops) 
   #:duplicates merge-generics) 
  
 (define-class <a> ()) 
 (define-class <b> ()) 
 (define-class <c> ()) 
  
 (define-method (action (o <c>)) 
   (display data) 
   (newline)) 
  
 (define data 1) 
  
 (define-method (action (o <a>)) 
   (display data) 
   (newline)) 
  
 (let ((data 9)) 
   (define-method (action (o <b>)) 
     (display data) 
     (newline))) 
  
 (define A (make <a>)) 
 (define B (make <b>)) 
 (define C (make <c>)) 
  
 (let ((data 2)) 
   (action A)) 
  
 (define data 3) 
  
 (action A) 
 (action (make <a>)) 
 (let ((data 2)) 
   (action B) 
   (action C)) 

we see that:

Inspection

Let's take this chunk of code:

 (define-module (test) 
   #:use-module (ice-9 pretty-print) 
   #:use-module (oop goops) 
   #:duplicates merge-generics) 
  
 (define-class <a> ()) 
 (define-class <b> (<a>)) 
 (define-class <c> (<b>)) 
 (define-class <d> ()) 
  
 (define-method (doit (o <a>) a b) 
   (format #t "doit/<a>~%")) 
  
 (define-method (doit (o <b>) c d) 
   (format #t "doit/<b>~%")) 
  
 (define-method (doit (o <c>) e) 
   (format #t "doit/<c>~%")) 
  
 (define-method (doit (o <d>)) 
   (format #t "doit/<d>~%")) 

here we define a hierarchy of 3 classes (<a>, <b>, <c>) and a lone class (<d>) each having its own implementation of the doit method. There is only 1 generic function, and in it there are 4 different methods.

One of the things that we want to show here is that we can think of a generic function as the following tree-like structure:

                                               -------------
                                           -->| specialiser |
 ------------------         ----------    |    -------------
| generic function |---+-->| method 1 |---+
 ------------------    |    ----------    |    ---------
                       |                   -->| closure |
                       |                       ---------
                       |
                       |                       -------------
                       |                   -->| specialiser |
                       |    ----------    |    -------------
                       +-->| method 2 |---+
                       |    ----------    |    ---------
                       |                   -->| closure |
                       |                       ---------
                       |
                       |                       -------------
                       |                   -->| specialiser |
                       |    ----------    |    -------------
                       +-->| method 3 |---+
                       .    ----------    |    ---------
                       .                   -->| closure |
                       .                       ---------

that is used as an associative container with the specialisers as keys and the closures has values. We do this using the inspection functions provided by GOOPS.


First of all we notice that a generic function is a value stored in a variable bound to the doit symbol, evaluating:

 (display doit)(newline) 
 (define other doit) 
 (display other)(newline) 

gives:

#<<generic> doit (4)>
#<<generic> doit (4)>

we see that: by applying display we can see the function's name and the current number of methods in it; we can set the function to any variable, and so use it to apply the function.

Another way to look at the function name is generic-function-name:

 (format #t "doit function name: '~A'~%" (generic-function-name doit)) 

outputs:

doit function name: 'doit'

the "official" name of the generic function is the symbol we used to define the function.


The list of methods in the collection can be retrieved with generic-function-methods, evaluating:

 (pretty-print (generic-function-methods doit)) 

we get something like:

(#<<method> (<d>) 403572a0>
 #<<method> (<c> <top>) 40357470>
 #<<method> (<b> <top> <top>) 40357720>
 #<<method> (<a> <top> <top>) 40357a50>)

we see that there are 4 methods each identified by a signature: the list of arguments' classes. In GOOPS's jargon the signature is called specialiser.


We can inspect the implementation of methods, too, using method-source:

 (map (lambda (method) 
        (pretty-print (method-source method))) 
   (generic-function-methods doit)) 

outputs:

(method ((o <d>)) (format #t "doit/<d>"))
(method
  ((o <c>) (e <top>))
  (format #t "doit/<c>"))
(method
  ((o <b>) (c <top>) (d <top>))
  (format #t "doit/<b>"))
(method
  ((o <a>) (a <top>) (b <top>))
  (format #t "doit/<a>"))

and we can retrieve the specialiser alone, for the second method in the list:

 (pretty-print 
  (method-specializers 
   (cadr (generic-function-methods doit)))) 

outputs:

(#<<class> <c> 40357e10>
 #<<class> <top> 4034def0>)

A generic function is an object itself, the hierarchy of classes has useful informations; evaluating:

 (format #t "class of 'doit': ~S~%" (class-of doit)) 
 (format #t "precedence list of 'doit':~%") 
 (pretty-print (class-precedence-list (class-of doit))) 

we get:

class of 'doit': #<<entity-class> <generic> 4034ce70>
precedence list of 'doit':
(#<<entity-class> <generic> 4034ce70>
 #<<entity-class> <entity> 4034cf70>
 #<<class> <object> 4034de70>
 #<<class> <applicable> 4034cfd0>
 #<<class> <top> 4034def0>)

so a generic function: it is of class <generic>, it is acceptable as <top> value, it has a special base class <applicable>. Notice that evaluating:

 (define (p q) #t) 
 (pretty-print (class-precedence-list (class-of p))) 

we get:

 (#<<procedure-class> <procedure> 4034c2b0> 
  #<<class> <applicable> 4034cfd0> 
  #<<class> <top> 4034def0>) 

so an ordinary procedure is of class <procedure>, different from <generic>, and has <applicable> in common with generic functions; this is a useful information because often we have to write procedures that must accept both an ordinary function and a generic function as parameter.

For example the following special map version accepts procedures but not generic functions:

 (define-method (map (f <procedure>) (o <my-collection>)) 
   ...) 

while the following accepts both ordinary and generic functions:

 (define-method (map (f <applicable>) (o <my-collection>)) 
   ...) 

A method is not an applicable object, but it holds the closure; to retrieve the procedure from a method's object we use method-procedure:

 (display (method-procedure (generic-function-methods doit))) 
 (newline) 

outputs:

#<procedure #f (o)>

which is the procedure of the (doit (o <d>)) method (the first in the list returned by generic-function-methods as it was shown before).

Methods invocation

Let's use the same chunk of code as before to see how methods inside generic functions are selected and invoked; we can play it with inspection functions. compute-applicable-methods returns the list of methods, inside a selected generic function, whose specialiser matches a list of arguments:

 (pretty-print 
  (compute-applicable-methods doit (list (make <a>) 1 2))) 

outputs:

(#<<method> (<a> <top> <top>) 40357a50>)
 (pretty-print 
  (compute-applicable-methods doit (list (make <b>) 1 2))) 

outputs:

(#<<method> (<b> <top> <top>) 403571c0>
 #<<method> (<a> <top> <top>) 40357430>)

here we get both the methods with <a> and <b> as first arguments because <a> is a superclass of <b>.


When more than one method in the collection do match a list of arguments, GOOPS needs a way to determine which one is "more specialised" for them; this is the job of method-more-specific?:

(let* ((args           (list (make <b>) 1 2))
       (args-classes   (map class-of args))
       (p              (compute-applicable-methods doit args))
       (the-one-for-b  (car p))
       (the-one-for-a  (cadr p)))
  (format #t "the one for <a>: ~A~%" the-one-for-a)
  (format #t "the one for <b>: ~A~%" the-one-for-b)
  (format #t "list of arguments' classes:~%")
  (pretty-print args-classes)
  (format #t "the one for <b> more specific than the one for <a>? ~A~%"
   (method-more-specific? the-one-for-b the-one-for-a args-classes)))

outputs:

the one for <a>: #<<method> (<a> <top> <top>) 40357430>
the one for <b>: #<<method> (<b> <top> <top>) 403571c0>
list of arguments' classes:
(#<<class> <b> 40357b60>
 #<<class> <integer> 4034c4f0>
 #<<class> <integer> 4034c4f0>)
the one for <b> more specific than the one for <a>? #t

In general, and when there are more than two applicable methods for a list of arguments, GOOPS needs a way to sort the methods from the more specific to the least specific, this is what sort-applicable-methods does:

 (let ((args (list (make <b>) 1 2))) 
   (pretty-print 
    (sort-applicable-methods doit 
     (compute-applicable-methods doit args) args))) 

outputs:

(#<<method> (<b> <top> <top>) 403571c0>
 #<<method> (<a> <top> <top>) 40357430>)

Now we have seen everything that GOOPS needs to do when we apply a generic function:

 (doit (make <b>) 1 2) 

outputs:

doit/<b>

What if no method is applicable? The following:

 (compute-applicable-methods doit (list 1 2 3)) 

returns #f and the invocation of doit returns an error.

What is next-method really?

To be written.

Overloading predefined ordinary functions

It is sometimes very useful to overload an ordinary Guile procedure with a generic function. When GOOPS is loaded some of the procedures are automatically converted to generics, others are not; example: at the REPL we see that:

$ guile
guile> (use-modules (oop goops))
guile> sin
#<procedure sin (z)>
guile> map
#<primitive-generic map>

that is: sin is still an ordinary procedure while map is ready to be overloaded.

To convert an ordinary procedure to a generic function we do this:

 (define saved-sin sin) 
 (define-generic sin) 
 (define-method (sin . args) 
   (apply saved-sin args)) 

now we can add methods:

 (define-method (sin (o <my-number>)) 
   ...) 

the use of define-generic is mandatory: it overwrites the value in the variable, while define-method raises an error.


This scheme of overloading works with any ordinary procedure, so we can put it in a macro:

 (define-macro (make-saved FUNC) 
   (let ((SAVED (string->symbol 
                 (string-append "saved-" (symbol->string FUNC))))) 
     `(begin 
       (define ,SAVED ,FUNC) 
       (define-generic ,FUNC) 
       (define-method (,FUNC . args) 
         (apply ,SAVED args))))) 

and invoke it like this:

 (make-saved sin) 

Generic functions and Guile modules

If we want to define generic functions in a module and use them from another, we just export them like we do for ordinary procedures:

 (define-module (this) 
   #:use-module (oop goops) 
   #:export (make-a doit)) 
  
 (define-class <a> ()) 
  
 (define (make-a) 
   (make <a>)) 
  
 (define-method (doit (o <a>)) 
   (format #t "doit/<a>~%")) 
  
 ;; -------------------------------------------------- 
  
 (define-module (that) 
   #:use-module (this)) 
  
 (doit (make-a)) 

as the script shows if we setup everything as required we do not need to load (oop goops) in the target module.


Let's see what happens if we define a generic function with the same name in two modules, without loading one into the other.

 (define-module (this) 
   #:use-module (ice-9 pretty-print) 
   #:use-module (oop goops)) 
  
 (define-class <a> ()) 
  
 (define (make-a) 
   (make <a>)) 
  
 (define-method (doit (o <a>)) 
   (format #t "doit/<a>~%")) 
  
 ;; -------------------------------------------------- 
  
 (define-module (that) 
   #:use-module (ice-9 pretty-print) 
   #:use-module (oop goops)) 
  
 (define-class <b> ()) 
  
 (define (make-b) 
   (make <b>)) 
  
 (define-method (doit (o <b>)) 
   (format #t "doit/<b>~%")) 
  
 (doit (make-b)) 
  
 (pretty-print (generic-function-methods doit)) 
  
 ;; ------------------------------------------------------------ 
  
 (set-current-module (resolve-module '(this))) 
  
 (doit (make-a)) 
  
 (pretty-print (generic-function-methods doit)) 

the output is:

doit/<b>
(#<<method> (<b>) 403574c0>)
doit/<a>
(#<<method> (<a>) 40357dc0>)

the two generic functions are left disjoint.


We have to understand what happens when we import a generic function in a module that already defines a generic function with the same name. Let's see what happens if we do nothing special:

 (define-module (this) 
   #:use-module (ice-9 pretty-print) 
   #:use-module (oop goops) 
   #:export (make-a doit)) 
  
 (define-class <a> ()) 
  
 (define (make-a) 
   (make <a>)) 
  
 (define-method (doit (o <a>)) 
   (format #t "doit/<a>~%")) 
  
 ;; -------------------------------------------------- 
  
 (define-module (that) 
   #:use-module (ice-9 pretty-print) 
   #:use-module (oop goops) 
   #:use-module (this)) 
  
 (define-class <b> ()) 
  
 (define (make-b) 
   (make <b>)) 
  
 (define-method (doit (o <b>)) 
   (format #t "doit/<b>~%")) 
  
 (doit (make-a)) 
 (doit (make-b)) 
  
 (pretty-print (generic-function-methods doit)) 
  
 ;; ------------------------------------------------------------ 
  
 (set-current-module (resolve-module '(this))) 
  
 (doit (make-a)) 
  
 (pretty-print (generic-function-methods doit)) 

outputs:

doit/<a>
doit/<b>
(#<<method> (<b>) 403569f0>
 #<<method> (<a>) 403571b0>)
doit/<a>
(#<<method> (<b>) 403569f0>
 #<<method> (<a>) 403571b0>)

so GOOPS joins the two generic functions into one: not only (doit (o <a>)) is visible in module (that), but also (doit (o <b>)) is visible in module (this).

We note explicitly that if we had used:

 (define-generic doit) 

in module (that), the result would have been the same.


Care must be taken when we overload an ordinary function and export the generic function from the module. Let's try this:

 (define-module (this) 
   #:use-module (ice-9 pretty-print) 
   #:use-module (oop goops) 
   #:export (sin)) 
  
 (define-macro (make-saved FUNC) 
   (let ((SAVED (string->symbol 
                 (string-append "saved-" (symbol->string FUNC))))) 
     `(begin 
       (define ,SAVED ,FUNC) 
       (define-generic ,FUNC) 
       (define-method (,FUNC . args) 
         (apply ,SAVED args))))) 
  
 (define-class <a> ()) 
  
 (define (make-a) 
   (make <a>)) 
  
 (make-saved sin) 
 (define-method (sin (o <a>)) 
   (format #t "sin/<a>~%")) 

the output is:

ERROR: Unbound variable: sin

what happened is that #:export (sin) has deleted the sin variable from the module, so the expansion of the macro has not found it. To solve this problem we have to:

 (define-module (this) 
   #:use-module (ice-9 pretty-print) 
   #:use-module (oop goops)) 
  
 (define-macro (make-saved FUNC) 
   (let ((SAVED (string->symbol 
                 (string-append "saved-" (symbol->string FUNC))))) 
     `(begin 
       (define ,SAVED ,FUNC) 
       (define-generic ,FUNC) 
       (define-method (,FUNC . args) 
         (apply ,SAVED args))))) 
  
 (define-class <a> ()) 
  
 (define (make-a) 
   (make <a>)) 
  
 (make-saved sin) 
 (define-method (sin (o <a>)) 
   (format #t "sin/<a>~%")) 
  
 (export make-a sin) 
  
 ;; -------------------------------------------------- 
  
 (define-module (that) 
   #:use-module (ice-9 pretty-print) 
   #:use-module (oop goops) 
   #:use-module (this) 
   #:duplicates merge-generics) 
  
 (define-class <b> ()) 
  
 (define (make-b) 
   (make <b>)) 
  
 (define-method (sin (o <b>)) 
   (format #t "sin/<b>~%")) 
  
 (display (sin 0.6))(newline) 
 (sin (make-a)) 
 (sin (make-b)) 

the output is:

0.564642473395035
sin/<a>
sin/<b>

As a general rule: always use export at the end of the module to export the generic functions, always use #:duplicates merge-generics when defining a module.


category-guile|category-goops