mirror of
https://git.savannah.gnu.org/git/guile.git
synced 2025-05-20 11:40:18 +02:00
Merge tutorial' and
reference' treatments of the same basic GOOPS
material * doc/ref/goops.texi (GOOPS): Move use of (oop goops) here. (Class Definition): Merged with `Defining New Classes' (Instance Creation): Insert before covering slot options. Merge in material from `Creating Instances'. (Slot Options): Merged some better wording and index entries from the tutorial version. (Slot Description Example): New node, containing the <my-complex> material from the tutorial. (Methods and Generic Functions, Inheritance): Tutorial sections moved into main line of the manual. * doc/ref/goops-tutorial.texi: Nothing left here now.
This commit is contained in:
parent
1dc43d57cc
commit
f60a835300
2 changed files with 729 additions and 970 deletions
|
@ -29,801 +29,3 @@
|
||||||
@c @macro guile @c was {\stk}
|
@c @macro guile @c was {\stk}
|
||||||
@c Guile
|
@c Guile
|
||||||
@c @end macro
|
@c @end macro
|
||||||
|
|
||||||
To start using @goops{} you first need to import the @code{(oop goops)}
|
|
||||||
module. You can do this at the Guile REPL by evaluating:
|
|
||||||
|
|
||||||
@lisp
|
|
||||||
(use-modules (oop goops))
|
|
||||||
@end lisp
|
|
||||||
@findex (oop goops)
|
|
||||||
|
|
||||||
@menu
|
|
||||||
* Class definition::
|
|
||||||
* Instance creation and slot access::
|
|
||||||
* Slot description::
|
|
||||||
* Inheritance::
|
|
||||||
* Generic functions::
|
|
||||||
@end menu
|
|
||||||
|
|
||||||
@node Class definition
|
|
||||||
@subsection Class definition
|
|
||||||
|
|
||||||
A new class is defined with the @code{define-class} syntax:
|
|
||||||
|
|
||||||
@findex define-class
|
|
||||||
@cindex class
|
|
||||||
@lisp
|
|
||||||
(define-class @var{class} (@var{superclass} @dots{})
|
|
||||||
@var{slot-description} @dots{}
|
|
||||||
@var{class-option} @dots{})
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
@var{class} is the class being defined. The list of @var{superclass}es
|
|
||||||
specifies which existing classes, if any, to inherit slots and
|
|
||||||
properties from. @dfn{Slots} hold per-instance@footnote{Usually --- but
|
|
||||||
see also the @code{#:allocation} slot option.} data, for instances of
|
|
||||||
that class --- like ``fields'' or ``member variables'' in other object
|
|
||||||
oriented systems. Each @var{slot-description} gives the name of a slot
|
|
||||||
and optionally some ``properties'' of this slot; for example its initial
|
|
||||||
value, the name of a function which will access its value, and so on.
|
|
||||||
Slot descriptions and inheritance are discussed more below. For class
|
|
||||||
options, see @ref{Class Options}.
|
|
||||||
@cindex slot
|
|
||||||
|
|
||||||
As an example, let us define a type for representing a complex number
|
|
||||||
in terms of two real numbers.@footnote{Of course Guile already
|
|
||||||
provides complex numbers, and @code{<complex>} is in fact a predefined
|
|
||||||
class in GOOPS; but the definition here is still useful as an
|
|
||||||
example.} This can be done with the following class definition:
|
|
||||||
|
|
||||||
@lisp
|
|
||||||
(define-class <my-complex> (<number>)
|
|
||||||
r i)
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
This binds the variable @code{<my-complex>} to a new class whose
|
|
||||||
instances will contain two slots. These slots are called @code{r} and
|
|
||||||
@code{i} and will hold the real and imaginary parts of a complex
|
|
||||||
number. Note that this class inherits from @code{<number>}, which is a
|
|
||||||
predefined class.@footnote{@code{<number>} is the direct superclass of
|
|
||||||
the predefined class @code{<complex>}; @code{<complex>} is the
|
|
||||||
superclass of @code{<real>}, and @code{<real>} is the superclass of
|
|
||||||
@code{<integer>}.}
|
|
||||||
|
|
||||||
@node Instance creation and slot access
|
|
||||||
@subsection Instance creation and slot access
|
|
||||||
|
|
||||||
Creation of an instance of a previously defined class can be done with
|
|
||||||
@code{make}. This procedure takes one mandatory parameter which is the
|
|
||||||
class of the instance which must be created, and a list of optional
|
|
||||||
arguments. Optional arguments are generally used to initialize some of
|
|
||||||
the slots of the newly created instance. For instance, the following
|
|
||||||
form
|
|
||||||
|
|
||||||
@findex make
|
|
||||||
@cindex instance
|
|
||||||
@lisp
|
|
||||||
(define c (make <my-complex>))
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
@noindent
|
|
||||||
will create a new @code{<my-complex>} object and will bind it to the @code{c}
|
|
||||||
Scheme variable.
|
|
||||||
|
|
||||||
Accessing the slots of the new complex number can be done with the
|
|
||||||
@code{slot-ref} and the @code{slot-set!} primitives. @code{slot-set!}
|
|
||||||
sets the value of an object slot and @code{slot-ref} retrieves it.
|
|
||||||
|
|
||||||
@findex slot-set!
|
|
||||||
@findex slot-ref
|
|
||||||
@lisp
|
|
||||||
@group
|
|
||||||
(slot-set! c 'r 10)
|
|
||||||
(slot-set! c 'i 3)
|
|
||||||
(slot-ref c 'r) @result{} 10
|
|
||||||
(slot-ref c 'i) @result{} 3
|
|
||||||
@end group
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
Using the @code{describe} function is a simple way to see all the
|
|
||||||
slots of an object at one time: this function prints all the slots of an
|
|
||||||
object on the standard output.
|
|
||||||
|
|
||||||
First load the module @code{(oop goops describe)}:
|
|
||||||
|
|
||||||
@example
|
|
||||||
@code{(use-modules (oop goops describe))}
|
|
||||||
@end example
|
|
||||||
|
|
||||||
@noindent
|
|
||||||
Then the expression
|
|
||||||
|
|
||||||
@lisp
|
|
||||||
(describe c)
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
@noindent
|
|
||||||
will print the following information on the standard output:
|
|
||||||
|
|
||||||
@smalllisp
|
|
||||||
#<<my-complex> 401d8638> is an instance of class <my-complex>
|
|
||||||
Slots are:
|
|
||||||
r = 10
|
|
||||||
i = 3
|
|
||||||
@end smalllisp
|
|
||||||
|
|
||||||
@node Slot description
|
|
||||||
@subsection Slot description
|
|
||||||
@c \label{slot-description}
|
|
||||||
|
|
||||||
When specifying a slot (in a @code{(define-class @dots{})} form),
|
|
||||||
various options can be specified in addition to the slot's name. Each
|
|
||||||
option is specified by a keyword. The list of authorized keywords is
|
|
||||||
given below:
|
|
||||||
|
|
||||||
@cindex keyword
|
|
||||||
@itemize @bullet
|
|
||||||
@item
|
|
||||||
@code{#:init-value} permits to supply a constant default value for the
|
|
||||||
slot. The value is obtained by evaluating the form given after the
|
|
||||||
@code{#:init-value} at class definition time.
|
|
||||||
@cindex default slot value
|
|
||||||
@findex #:init-value
|
|
||||||
|
|
||||||
@item
|
|
||||||
@code{#:init-form} specifies a form that, when evaluated, will return
|
|
||||||
an initial value for the slot. The form is evaluated each time that
|
|
||||||
an instance of the class is created, in the lexical environment of the
|
|
||||||
containing @code{define-class} expression.
|
|
||||||
@cindex default slot value
|
|
||||||
@findex #:init-form
|
|
||||||
|
|
||||||
@item
|
|
||||||
@code{#:init-thunk} permits to supply a thunk that will provide a
|
|
||||||
default value for the slot. The value is obtained by invoking the
|
|
||||||
thunk at instance creation time.
|
|
||||||
@findex default slot value
|
|
||||||
@findex #:init-thunk
|
|
||||||
|
|
||||||
@item
|
|
||||||
@code{#:init-keyword} permits to specify a keyword for initializing the
|
|
||||||
slot. The init-keyword may be provided during instance creation (i.e. in
|
|
||||||
the @code{make} optional parameter list). Specifying such a keyword
|
|
||||||
during instance initialization will supersede the default slot
|
|
||||||
initialization possibly given with @code{#:init-form}.
|
|
||||||
@findex #:init-keyword
|
|
||||||
|
|
||||||
@item
|
|
||||||
@code{#:getter} permits to supply the name for the
|
|
||||||
slot getter. The name binding is done in the
|
|
||||||
environment of the @code{define-class} macro.
|
|
||||||
@findex #:getter
|
|
||||||
@cindex top level environment
|
|
||||||
@cindex getter
|
|
||||||
|
|
||||||
@item
|
|
||||||
@code{#:setter} permits to supply the name for the
|
|
||||||
slot setter. The name binding is done in the
|
|
||||||
environment of the @code{define-class} macro.
|
|
||||||
@findex #:setter
|
|
||||||
@cindex top level environment
|
|
||||||
@cindex setter
|
|
||||||
|
|
||||||
@item
|
|
||||||
@code{#:accessor} permits to supply the name for the
|
|
||||||
slot accessor. The name binding is done in the global
|
|
||||||
environment. An accessor permits to get and
|
|
||||||
set the value of a slot. Setting the value of a slot is done with the extended
|
|
||||||
version of @code{set!}.
|
|
||||||
@findex set!
|
|
||||||
@findex #:accessor
|
|
||||||
@cindex top level environment
|
|
||||||
@cindex accessor
|
|
||||||
|
|
||||||
@item
|
|
||||||
@code{#:allocation} permits to specify how storage for
|
|
||||||
the slot is allocated. Three kinds of allocation are provided.
|
|
||||||
They are described below:
|
|
||||||
|
|
||||||
@itemize @minus
|
|
||||||
@item
|
|
||||||
@code{#:instance} indicates that each instance gets its own storage for
|
|
||||||
the slot. This is the default.
|
|
||||||
@item
|
|
||||||
@code{#:class} indicates that there is one storage location used by all
|
|
||||||
the direct and indirect instances of the class. This permits to define a
|
|
||||||
kind of global variable which can be accessed only by (in)direct
|
|
||||||
instances of the class which defines this slot.
|
|
||||||
@item
|
|
||||||
@code{#:each-subclass} indicates that there is one storage location used
|
|
||||||
by all the direct instances of the class. In other words, if two classes
|
|
||||||
are not siblings in the class hierarchy, they will not see the same
|
|
||||||
value.
|
|
||||||
@item
|
|
||||||
@code{#:virtual} indicates that no storage will be allocated for this
|
|
||||||
slot. It is up to the user to define a getter and a setter function for
|
|
||||||
this slot. Those functions must be defined with the @code{#:slot-ref}
|
|
||||||
and @code{#:slot-set!} options. See the example below.
|
|
||||||
@findex #:slot-set!
|
|
||||||
@findex #:slot-ref
|
|
||||||
@findex #:virtual
|
|
||||||
@findex #:class
|
|
||||||
@findex #:each-subclass
|
|
||||||
@findex #:instance
|
|
||||||
@findex #:allocation
|
|
||||||
@end itemize
|
|
||||||
@end itemize
|
|
||||||
|
|
||||||
To illustrate slot description, we shall redefine the @code{<my-complex>} class
|
|
||||||
seen before. A definition could be:
|
|
||||||
|
|
||||||
@lisp
|
|
||||||
(define-class <my-complex> (<number>)
|
|
||||||
(r #:init-value 0 #:getter get-r #:setter set-r! #:init-keyword #:r)
|
|
||||||
(i #:init-value 0 #:getter get-i #:setter set-i! #:init-keyword #:i))
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
With this definition, the @code{r} and @code{i} slot are set to 0 by
|
|
||||||
default. Value of a slot can also be specified by calling @code{make}
|
|
||||||
with the @code{#:r} and @code{#:i} keywords. Furthermore, the generic
|
|
||||||
functions @code{get-r} and @code{set-r!} (resp. @code{get-i} and
|
|
||||||
@code{set-i!}) are automatically defined by the system to read and write
|
|
||||||
the @code{r} (resp. @code{i}) slot.
|
|
||||||
|
|
||||||
@lisp
|
|
||||||
(define c1 (make <my-complex> #:r 1 #:i 2))
|
|
||||||
(get-r c1) @result{} 1
|
|
||||||
(set-r! c1 12)
|
|
||||||
(get-r c1) @result{} 12
|
|
||||||
(define c2 (make <my-complex> #:r 2))
|
|
||||||
(get-r c2) @result{} 2
|
|
||||||
(get-i c2) @result{} 0
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
Accessors provide an uniform access for reading and writing an object
|
|
||||||
slot. Writing a slot is done with an extended form of @code{set!}
|
|
||||||
which is close to the Common Lisp @code{setf} macro. So, another
|
|
||||||
definition of the previous @code{<my-complex>} class, using the
|
|
||||||
@code{#:accessor} option, could be:
|
|
||||||
|
|
||||||
@findex set!
|
|
||||||
@lisp
|
|
||||||
(define-class <my-complex> (<number>)
|
|
||||||
(r #:init-value 0 #:accessor real-part #:init-keyword #:r)
|
|
||||||
(i #:init-value 0 #:accessor imag-part #:init-keyword #:i))
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
Using this class definition, reading the real part of the @code{c}
|
|
||||||
complex can be done with:
|
|
||||||
@lisp
|
|
||||||
(real-part c)
|
|
||||||
@end lisp
|
|
||||||
and setting it to the value contained in the @code{new-value} variable
|
|
||||||
can be done using the extended form of @code{set!}.
|
|
||||||
@lisp
|
|
||||||
(set! (real-part c) new-value)
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
Suppose now that we have to manipulate complex numbers with rectangular
|
|
||||||
coordinates as well as with polar coordinates. One solution could be to
|
|
||||||
have a definition of complex numbers which uses one particular
|
|
||||||
representation and some conversion functions to pass from one
|
|
||||||
representation to the other. A better solution uses virtual slots. A
|
|
||||||
complete definition of the @code{<my-complex>} class using virtual slots is
|
|
||||||
given in Figure@ 2.
|
|
||||||
|
|
||||||
@example
|
|
||||||
@group
|
|
||||||
@lisp
|
|
||||||
(define-class <my-complex> (<number>)
|
|
||||||
;; True slots use rectangular coordinates
|
|
||||||
(r #:init-value 0 #:accessor real-part #:init-keyword #:r)
|
|
||||||
(i #:init-value 0 #:accessor imag-part #:init-keyword #:i)
|
|
||||||
;; Virtual slots access do the conversion
|
|
||||||
(m #:accessor magnitude #:init-keyword #:magn
|
|
||||||
#:allocation #:virtual
|
|
||||||
#:slot-ref (lambda (o)
|
|
||||||
(let ((r (slot-ref o 'r)) (i (slot-ref o 'i)))
|
|
||||||
(sqrt (+ (* r r) (* i i)))))
|
|
||||||
#:slot-set! (lambda (o m)
|
|
||||||
(let ((a (slot-ref o 'a)))
|
|
||||||
(slot-set! o 'r (* m (cos a)))
|
|
||||||
(slot-set! o 'i (* m (sin a))))))
|
|
||||||
(a #:accessor angle #:init-keyword #:angle
|
|
||||||
#:allocation #:virtual
|
|
||||||
#:slot-ref (lambda (o)
|
|
||||||
(atan (slot-ref o 'i) (slot-ref o 'r)))
|
|
||||||
#:slot-set! (lambda(o a)
|
|
||||||
(let ((m (slot-ref o 'm)))
|
|
||||||
(slot-set! o 'r (* m (cos a)))
|
|
||||||
(slot-set! o 'i (* m (sin a)))))))
|
|
||||||
|
|
||||||
@end lisp
|
|
||||||
@center @emph{Fig 2: A @code{<my-complex>} number class definition using virtual slots}
|
|
||||||
@end group
|
|
||||||
@end example
|
|
||||||
|
|
||||||
@sp 3
|
|
||||||
This class definition implements two real slots (@code{r} and
|
|
||||||
@code{i}). Values of the @code{m} and @code{a} virtual slots are
|
|
||||||
calculated from real slot values. Reading a virtual slot leads to the
|
|
||||||
application of the function defined in the @code{#:slot-ref}
|
|
||||||
option. Writing such a slot leads to the application of the function
|
|
||||||
defined in the @code{#:slot-set!} option. For instance, the following
|
|
||||||
expression
|
|
||||||
|
|
||||||
@findex #:slot-set!
|
|
||||||
@findex #:slot-ref
|
|
||||||
@lisp
|
|
||||||
(slot-set! c 'a 3)
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
permits to set the angle of the @code{c} complex number. This expression
|
|
||||||
conducts, in fact, to the evaluation of the following expression
|
|
||||||
|
|
||||||
@lisp
|
|
||||||
((lambda o m)
|
|
||||||
(let ((m (slot-ref o 'm)))
|
|
||||||
(slot-set! o 'r (* m (cos a)))
|
|
||||||
(slot-set! o 'i (* m (sin a))))
|
|
||||||
c 3)
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
A more complete example is given below:
|
|
||||||
|
|
||||||
@example
|
|
||||||
@group
|
|
||||||
@smalllisp
|
|
||||||
(define c (make <my-complex> #:r 12 #:i 20))
|
|
||||||
(real-part c) @result{} 12
|
|
||||||
(angle c) @result{} 1.03037682652431
|
|
||||||
(slot-set! c 'i 10)
|
|
||||||
(set! (real-part c) 1)
|
|
||||||
(describe c)
|
|
||||||
@print{}
|
|
||||||
#<<my-complex> 401e9b58> is an instance of class <my-complex>
|
|
||||||
Slots are:
|
|
||||||
r = 1
|
|
||||||
i = 10
|
|
||||||
m = 10.0498756211209
|
|
||||||
a = 1.47112767430373
|
|
||||||
@end smalllisp
|
|
||||||
@end group
|
|
||||||
@end example
|
|
||||||
|
|
||||||
Since initialization keywords have been defined for the four slots, we
|
|
||||||
can now define the @code{make-rectangular} and @code{make-polar} standard
|
|
||||||
Scheme primitives.
|
|
||||||
|
|
||||||
@lisp
|
|
||||||
(define make-rectangular
|
|
||||||
(lambda (x y) (make <my-complex> #:r x #:i y)))
|
|
||||||
|
|
||||||
(define make-polar
|
|
||||||
(lambda (x y) (make <my-complex> #:magn x #:angle y)))
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
@node Inheritance
|
|
||||||
@subsection Inheritance
|
|
||||||
@c \label{inheritance}
|
|
||||||
|
|
||||||
@menu
|
|
||||||
* Class hierarchy and inheritance of slots::
|
|
||||||
* Class precedence list::
|
|
||||||
@end menu
|
|
||||||
|
|
||||||
@node Class hierarchy and inheritance of slots
|
|
||||||
@subsubsection Class hierarchy and inheritance of slots
|
|
||||||
Inheritance is specified upon class definition. As said in the
|
|
||||||
introduction, @goops{} supports multiple inheritance. Here are some
|
|
||||||
class definitions:
|
|
||||||
|
|
||||||
@lisp
|
|
||||||
(define-class A () a)
|
|
||||||
(define-class B () b)
|
|
||||||
(define-class C () c)
|
|
||||||
(define-class D (A B) d a)
|
|
||||||
(define-class E (A C) e c)
|
|
||||||
(define-class F (D E) f)
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
@code{A}, @code{B}, @code{C} have a null list of super classes. In this
|
|
||||||
case, the system will replace it by the list which only contains
|
|
||||||
@code{<object>}, the root of all the classes defined by
|
|
||||||
@code{define-class}. @code{D}, @code{E}, @code{F} use multiple
|
|
||||||
inheritance: each class inherits from two previously defined classes.
|
|
||||||
Those class definitions define a hierarchy which is shown in Figure@ 1.
|
|
||||||
In this figure, the class @code{<top>} is also shown; this class is the
|
|
||||||
super class of all Scheme objects. In particular, @code{<top>} is the
|
|
||||||
super class of all standard Scheme types.
|
|
||||||
|
|
||||||
@example
|
|
||||||
@group
|
|
||||||
@iftex
|
|
||||||
@center @image{hierarchy,5in}
|
|
||||||
@end iftex
|
|
||||||
@ifnottex
|
|
||||||
@verbatiminclude hierarchy.txt
|
|
||||||
@end ifnottex
|
|
||||||
|
|
||||||
@emph{Fig 1: A class hierarchy}
|
|
||||||
@emph{(@code{<complex>} which is the direct subclass of @code{<number>}
|
|
||||||
and the direct superclass of @code{<real>} has been omitted in this
|
|
||||||
figure.)}
|
|
||||||
@end group
|
|
||||||
@end example
|
|
||||||
|
|
||||||
The set of slots of a given class is calculated by taking the union of the
|
|
||||||
slots of all its super class. For instance, each instance of the class
|
|
||||||
D, defined before will have three slots (@code{a}, @code{b} and
|
|
||||||
@code{d}). The slots of a class can be obtained by the @code{class-slots}
|
|
||||||
primitive. For instance,
|
|
||||||
|
|
||||||
@lisp
|
|
||||||
(class-slots A) @result{} ((a))
|
|
||||||
(class-slots E) @result{} ((a) (e) (c))
|
|
||||||
(class-slots F) @result{} ((e) (c) (b) (d) (a) (f))
|
|
||||||
@c used to be ((d) (a) (b) (c) (f))
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
@emph{Note: } The order of slots is not significant.
|
|
||||||
|
|
||||||
@node Class precedence list
|
|
||||||
@subsubsection Class precedence list
|
|
||||||
|
|
||||||
A class may have more than one superclass. @footnote{This section is an
|
|
||||||
adaptation of Jeff Dalton's (J.Dalton@@ed.ac.uk) @cite{Brief
|
|
||||||
introduction to CLOS}} With single inheritance (one superclass), it is
|
|
||||||
easy to order the super classes from most to least specific. This is the
|
|
||||||
rule:
|
|
||||||
|
|
||||||
@display
|
|
||||||
@cartouche
|
|
||||||
Rule 1: Each class is more specific than its superclasses.@c was \bf
|
|
||||||
@end cartouche
|
|
||||||
@end display
|
|
||||||
|
|
||||||
With multiple inheritance, ordering is harder. Suppose we have
|
|
||||||
|
|
||||||
@lisp
|
|
||||||
(define-class X ()
|
|
||||||
(x #:init-value 1))
|
|
||||||
|
|
||||||
(define-class Y ()
|
|
||||||
(x #:init-value 2))
|
|
||||||
|
|
||||||
(define-class Z (X Y)
|
|
||||||
(@dots{}))
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
In this case, the @code{Z} class is more specific than the @code{X} or
|
|
||||||
@code{Y} class for instances of @code{Z}. However, the @code{#:init-value}
|
|
||||||
specified in @code{X} and @code{Y} leads to a problem: which one
|
|
||||||
overrides the other? The rule in @goops{}, as in CLOS, is that the
|
|
||||||
superclasses listed earlier are more specific than those listed later.
|
|
||||||
So:
|
|
||||||
|
|
||||||
@display
|
|
||||||
@cartouche
|
|
||||||
Rule 2: For a given class, superclasses listed earlier are more
|
|
||||||
specific than those listed later.
|
|
||||||
@end cartouche
|
|
||||||
@end display
|
|
||||||
|
|
||||||
These rules are used to compute a linear order for a class and all its
|
|
||||||
superclasses, from most specific to least specific. This order is
|
|
||||||
called the ``class precedence list'' of the class. Given these two
|
|
||||||
rules, we can claim that the initial form for the @code{x} slot of
|
|
||||||
previous example is 1 since the class @code{X} is placed before @code{Y}
|
|
||||||
in class precedence list of @code{Z}.
|
|
||||||
|
|
||||||
These two rules are not always enough to determine a unique order,
|
|
||||||
however, but they give an idea of how things work. Taking the @code{F}
|
|
||||||
class shown in Figure@ 1, the class precedence list is
|
|
||||||
|
|
||||||
@example
|
|
||||||
(f d e a c b <object> <top>)
|
|
||||||
@end example
|
|
||||||
|
|
||||||
However, it is usually considered a bad idea for programmers to rely on
|
|
||||||
exactly what the order is. If the order for some superclasses is important,
|
|
||||||
it can be expressed directly in the class definition.
|
|
||||||
|
|
||||||
The precedence list of a class can be obtained by the function
|
|
||||||
@code{class-precedence-list}. This function returns a ordered
|
|
||||||
list whose first element is the most specific class. For instance,
|
|
||||||
|
|
||||||
@lisp
|
|
||||||
(class-precedence-list B) @result{} (#<<class> B 401b97c8>
|
|
||||||
#<<class> <object> 401e4a10>
|
|
||||||
#<<class> <top> 4026a9d8>)
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
However, this result is not too much readable; using the function
|
|
||||||
@code{class-name} yields a clearer result:
|
|
||||||
|
|
||||||
@lisp
|
|
||||||
(map class-name (class-precedence-list B)) @result{} (B <object> <top>)
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
@node Generic functions
|
|
||||||
@subsection 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.
|
|
||||||
|
|
||||||
@lisp
|
|
||||||
(define-method (+ (x <string>) (y <string>))
|
|
||||||
(string-append x y))
|
|
||||||
|
|
||||||
(+ "abc" "de") @result{} "abcde"
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
@lisp
|
|
||||||
(define-method (stretch (im <image>) (sf <surface>))
|
|
||||||
...)
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
@noindent
|
|
||||||
and the question of which class the method is more associated with does
|
|
||||||
not need answering.
|
|
||||||
|
|
||||||
A generic function is a collection of methods with the same name but
|
|
||||||
different sets of specializing argument classes.
|
|
||||||
|
|
||||||
@menu
|
|
||||||
* Generic functions and methods::
|
|
||||||
* Next-method::
|
|
||||||
* Example::
|
|
||||||
@end menu
|
|
||||||
|
|
||||||
@node Generic functions and methods
|
|
||||||
@subsubsection Generic functions and methods
|
|
||||||
|
|
||||||
@c \label{gf-n-methods}
|
|
||||||
Neither @goops{} nor CLOS use the message mechanism for methods as most
|
|
||||||
Object Oriented language do. Instead, they use the notion of
|
|
||||||
@dfn{generic functions}. A generic function can be seen as a methods
|
|
||||||
``tanker''. When the evaluator requested the application of a generic
|
|
||||||
function, all the methods of this generic function will be grabbed and
|
|
||||||
the most specific among them will be applied. We say that a method
|
|
||||||
@var{M} is @emph{more specific} than a method @var{M'} if the class of
|
|
||||||
its parameters are more specific than the @var{M'} ones. To be more
|
|
||||||
precise, when a generic function must be ``called'' the system will:
|
|
||||||
|
|
||||||
@cindex generic function
|
|
||||||
@enumerate
|
|
||||||
@item
|
|
||||||
search among all the generic function those which are applicable
|
|
||||||
@item
|
|
||||||
sort the list of applicable methods in the ``most specific'' order
|
|
||||||
@item
|
|
||||||
call the most specific method of this list (i.e. the first method of
|
|
||||||
the sorted methods list).
|
|
||||||
@end enumerate
|
|
||||||
|
|
||||||
The definition of a generic function is done with the
|
|
||||||
@code{define-generic} macro. Definition of a new method is done with the
|
|
||||||
@code{define-method} macro. Note that @code{define-method} automatically
|
|
||||||
defines the generic function if it has not been defined
|
|
||||||
before. Consequently, most of the time, the @code{define-generic} needs
|
|
||||||
not be used.
|
|
||||||
@findex define-generic
|
|
||||||
@findex define-method
|
|
||||||
Consider the following definitions:
|
|
||||||
|
|
||||||
@lisp
|
|
||||||
(define-generic G)
|
|
||||||
(define-method (G (a <integer>) b) 'integer)
|
|
||||||
(define-method (G (a <real>) b) 'real)
|
|
||||||
(define-method (G a b) 'top)
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
The @code{define-generic} call defines @var{G} as a generic
|
|
||||||
function. Note that the signature of the generic function is not given
|
|
||||||
upon definition, contrarily to CLOS. This will permit methods with
|
|
||||||
different signatures for a given generic function, as we shall see
|
|
||||||
later. The three next lines define methods for the @var{G} generic
|
|
||||||
function. Each method uses a sequence of @dfn{parameter specializers}
|
|
||||||
that specify when the given method is applicable. A specializer permits
|
|
||||||
to indicate the class a parameter must belong to (directly or
|
|
||||||
indirectly) to be applicable. If no specializer is given, the system
|
|
||||||
defaults it to @code{<top>}. Thus, the first method definition is
|
|
||||||
equivalent to
|
|
||||||
|
|
||||||
@cindex parameter specializers
|
|
||||||
@lisp
|
|
||||||
(define-method (G (a <integer>) (b <top>)) 'integer)
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
Now, let us look at some possible calls to generic function @var{G}:
|
|
||||||
|
|
||||||
@lisp
|
|
||||||
(G 2 3) @result{} integer
|
|
||||||
(G 2 #t) @result{} integer
|
|
||||||
(G 1.2 'a) @result{} real
|
|
||||||
@c (G #3 'a) @result{} real @c was {\sharpsign}
|
|
||||||
(G #t #f) @result{} top
|
|
||||||
(G 1 2 3) @result{} error (since no method exists for 3 parameters)
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
The preceding methods use only one specializer per parameter list. Of
|
|
||||||
course, each parameter can use a specializer. In this case, the
|
|
||||||
parameter list is scanned from left to right to determine the
|
|
||||||
applicability of a method. Suppose we declare now
|
|
||||||
|
|
||||||
@lisp
|
|
||||||
(define-method (G (a <integer>) (b <number>)) 'integer-number)
|
|
||||||
(define-method (G (a <integer>) (b <real>)) 'integer-real)
|
|
||||||
(define-method (G (a <integer>) (b <integer>)) 'integer-integer)
|
|
||||||
(define-method (G a (b <number>)) 'top-number)
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
In this case,
|
|
||||||
|
|
||||||
@lisp
|
|
||||||
(G 1 2) @result{} integer-integer
|
|
||||||
(G 1 1.0) @result{} integer-real
|
|
||||||
(G 1 #t) @result{} integer
|
|
||||||
(G 'a 1) @result{} top-number
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
@node Next-method
|
|
||||||
@subsubsection Next-method
|
|
||||||
|
|
||||||
When you call a generic function, with a particular set of arguments,
|
|
||||||
GOOPS builds a list of all the methods that are applicable to those
|
|
||||||
arguments and orders them by how closely the method definitions match
|
|
||||||
the actual argument types. It then calls the method at the top of this
|
|
||||||
list. If the selected method's code wants to call on to the next method
|
|
||||||
in this list, it can do so by using @code{next-method}.
|
|
||||||
|
|
||||||
@lisp
|
|
||||||
(define-method (Test (a <integer>)) (cons 'integer (next-method)))
|
|
||||||
(define-method (Test (a <number>)) (cons 'number (next-method)))
|
|
||||||
(define-method (Test a) (list 'top))
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
With these definitions,
|
|
||||||
|
|
||||||
@lisp
|
|
||||||
(Test 1) @result{} (integer number top)
|
|
||||||
(Test 1.0) @result{} (number top)
|
|
||||||
(Test #t) @result{} (top)
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
@code{next-method} is always called as just @code{(next-method)}. The
|
|
||||||
arguments for the next method call are always implicit, and always the
|
|
||||||
same as for the original method call.
|
|
||||||
|
|
||||||
If you want to call on to a method with the same name but with a
|
|
||||||
different set of arguments (as you might with overloaded methods in C++,
|
|
||||||
for example), you do not use @code{next-method}, but instead simply
|
|
||||||
write the new call as usual:
|
|
||||||
|
|
||||||
@lisp
|
|
||||||
(define-method (Test (a <number>) min max)
|
|
||||||
(if (and (>= a min) (<= a max))
|
|
||||||
(display "Number is in range\n"))
|
|
||||||
(Test a))
|
|
||||||
|
|
||||||
(Test 2 1 10)
|
|
||||||
@print{}
|
|
||||||
Number is in range
|
|
||||||
@result{}
|
|
||||||
(integer number top)
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
(You should be careful in this case that the @code{Test} calls do not
|
|
||||||
lead to an infinite recursion, but this consideration is just the same
|
|
||||||
as in Scheme code in general.)
|
|
||||||
|
|
||||||
@node Example
|
|
||||||
@subsubsection Example
|
|
||||||
|
|
||||||
In this section we shall continue to define operations on the @code{<my-complex>}
|
|
||||||
class defined in Figure@ 2. Suppose that we want to use it to implement
|
|
||||||
complex numbers completely. For instance a definition for the addition of
|
|
||||||
two complexes could be
|
|
||||||
|
|
||||||
@lisp
|
|
||||||
(define-method (new-+ (a <my-complex>) (b <my-complex>))
|
|
||||||
(make-rectangular (+ (real-part a) (real-part b))
|
|
||||||
(+ (imag-part a) (imag-part b))))
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
To be sure that the @code{+} used in the method @code{new-+} is the standard
|
|
||||||
addition we can do:
|
|
||||||
|
|
||||||
@lisp
|
|
||||||
(define-generic new-+)
|
|
||||||
|
|
||||||
(let ((+ +))
|
|
||||||
(define-method (new-+ (a <my-complex>) (b <my-complex>))
|
|
||||||
(make-rectangular (+ (real-part a) (real-part b))
|
|
||||||
(+ (imag-part a) (imag-part b)))))
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
The @code{define-generic} ensures here that @code{new-+} will be defined
|
|
||||||
in the global environment. Once this is done, we can add methods to the
|
|
||||||
generic function @code{new-+} which make a closure on the @code{+}
|
|
||||||
symbol. A complete writing of the @code{new-+} methods is shown in
|
|
||||||
Figure@ 3.
|
|
||||||
|
|
||||||
@example
|
|
||||||
@group
|
|
||||||
@lisp
|
|
||||||
(define-generic new-+)
|
|
||||||
|
|
||||||
(let ((+ +))
|
|
||||||
|
|
||||||
(define-method (new-+ (a <real>) (b <real>)) (+ a b))
|
|
||||||
|
|
||||||
(define-method (new-+ (a <real>) (b <my-complex>))
|
|
||||||
(make-rectangular (+ a (real-part b)) (imag-part b)))
|
|
||||||
|
|
||||||
(define-method (new-+ (a <my-complex>) (b <real>))
|
|
||||||
(make-rectangular (+ (real-part a) b) (imag-part a)))
|
|
||||||
|
|
||||||
(define-method (new-+ (a <my-complex>) (b <my-complex>))
|
|
||||||
(make-rectangular (+ (real-part a) (real-part b))
|
|
||||||
(+ (imag-part a) (imag-part b))))
|
|
||||||
|
|
||||||
(define-method (new-+ (a <number>)) a)
|
|
||||||
|
|
||||||
(define-method (new-+) 0)
|
|
||||||
|
|
||||||
(define-method (new-+ . args)
|
|
||||||
(new-+ (car args)
|
|
||||||
(apply new-+ (cdr args)))))
|
|
||||||
|
|
||||||
(set! + new-+)
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
@center @emph{Fig 3: Extending @code{+} for dealing with complex numbers}
|
|
||||||
@end group
|
|
||||||
@end example
|
|
||||||
|
|
||||||
@sp 3
|
|
||||||
We use here the fact that generic function are not obliged to have the
|
|
||||||
same number of parameters, contrarily to CLOS. The four first methods
|
|
||||||
implement the dyadic addition. The fifth method says that the addition
|
|
||||||
of a single element is this element itself. The sixth method says that
|
|
||||||
using the addition with no parameter always return 0. The last method
|
|
||||||
takes an arbitrary number of parameters@footnote{The parameter list for
|
|
||||||
a @code{define-method} follows the conventions used for Scheme
|
|
||||||
procedures. In particular it can use the dot notation or a symbol to
|
|
||||||
denote an arbitrary number of parameters}. This method acts as a kind
|
|
||||||
of @code{reduce}: it calls the dyadic addition on the @emph{car} of the
|
|
||||||
list and on the result of applying it on its rest. To finish, the
|
|
||||||
@code{set!} permits to redefine the @code{+} symbol to our extended
|
|
||||||
addition.
|
|
||||||
|
|
||||||
@sp 3
|
|
||||||
To terminate our implementation (integration?) of complex numbers, we can
|
|
||||||
redefine standard Scheme predicates in the following manner:
|
|
||||||
|
|
||||||
@lisp
|
|
||||||
(define-method (complex? c <my-complex>) #t)
|
|
||||||
(define-method (complex? c) #f)
|
|
||||||
|
|
||||||
(define-method (number? n <number>) #t)
|
|
||||||
(define-method (number? n) #f)
|
|
||||||
@dots{}
|
|
||||||
@dots{}
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
Standard primitives in which complex numbers are involved could also be
|
|
||||||
redefined in the same manner.
|
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue