8.6.3 Merging Generics

GOOPS generic functions and accessors often have short, generic names. For example, if a vector package provides an accessor for the X coordinate of a vector, that accessor may just be called x. It doesn’t need to be called, for example, vector:x, because GOOPS will work out, when it sees code like (x obj), that the vector-specific method of x should be called if obj is a vector.

That raises the question, though, of what happens when different packages define a generic function with the same name. Suppose we work with a graphical package which needs to use two independent vector packages for 2D and 3D vectors respectively. If both packages export x, what does the code using those packages end up with?

duplicate binding handlers explains how this is resolved for conflicting bindings in general. For generics, there is a special duplicates handler, merge-generics, which tells the module system to merge generic functions with the same name. Here is an example:

(define-module (math 2D-vectors)
  #:use-module (oop goops)
  #:export (x y ...))
		  
(define-module (math 3D-vectors)
  #:use-module (oop goops)
  #:export (x y z ...))

(define-module (my-module)
  #:use-module (oop goops)
  #:use-module (math 2D-vectors)
  #:use-module (math 3D-vectors)
  #:duplicates (merge-generics))

The generic function x in (my-module) will now incorporate all of the methods of x from both imported modules.

To be precise, there will now be three distinct generic functions named x: x in (math 2D-vectors), x in (math 3D-vectors), and x in (my-module); and these functions share their methods in an interesting and dynamic way.

To explain, let’s call the imported generic functions (in (math 2D-vectors) and (math 3D-vectors)) the ancestors, and the merged generic function (in (my-module)), the descendant. The general rule is that for any generic function G, the applicable methods are selected from the union of the methods of G’s descendant functions, the methods of G itself and the methods of G’s ancestor functions.

Thus ancestor functions effectively share methods with their descendants, and vice versa. In the example above, x in (math 2D-vectors) will share the methods of x in (my-module) and vice versa.32 Sharing is dynamic, so adding another new method to a descendant implies adding it to that descendant’s ancestors too.


Footnotes

(32)

But note that x in (math 2D-vectors) doesn’t share methods with x in (math 3D-vectors), so modularity is still preserved.