8. STklos Object System
8.1. Introduction
The aim of this chapter is to present STklos object system. Briefly stated, STklos gives the programmer an extensive object system with meta-classes, multiple inheritance, generic functions and multi-methods. Furthermore, its implementation relies on a MOP (Meta Object Protocol (MOP) [AMOP]), in the spirit of the one defined for CLOS [CLtL2].
STklos implementation is derived from the version 1.3 of Tiny CLOS, a pure and clean CLOS-like MOP implementation in Scheme written by Gregor Kickzales [Tiny-Clos]. However, Tiny CLOS implementation was designed as a pedagogical tool and consequently, completeness and efficiency were not the author concern for it. STklos extends the Tiny CLOS model to be efficient and as close as possible to CLOS, the Common Lisp Object System [CLtL2]. Some features of STklos are also issued from [Dylan] or [SOS].
This chapter is divided in three parts, which have a quite different audience in mind:
-
The first part presents the STklos object system rather informally; it is intended to be a tutorial of the language and is for people who want to have an idea of the look and feel of STklos.
-
The second part describes the STklos object system at the external level (i.e. without requiring the use of the Meta Object Protocol).
-
The third and last part describes the STklos Meta Object Protocol. It is intended for people whio want to play with meta programming.
8.2. Object System Tutorial
The STklos object system relies on classes like most of the current OO languages. Furthermore, STklos provides meta-classes, multiple inheritance, generic functions and multi-methods as in CLOS, the Common Lisp Object System [CLtL2] or [Dylan]. This chapter presents STklos in a rather informal manner. Its intent is to give the reader an idea of the "look and feel" of STklos programming. However, we suppose here that the reader has some basic notions of OO programming, and is familiar with terms such as classes, instances or methods.
8.2.1. Class definition and instantiation
Class definition
A new class is defined with the define-class
form.
The syntax of define-class
is close to CLOS defclass
:
(define-class class (superclass~1~ superclass~2~ ...)
(slot-description1
slot-description2
...)
metaclass option)
The metaclass option will not be discussed here. The superclasses list specifies the super classes of class (see Section 8.2.2 for details).
A slot description gives the name of a slot and, eventually, some properties of this slot (such as its initial value, the function which permit to access its value, …). Slot descriptions will be discussed in ,(index "slot-definition").
As an example, consider now that we want to define a point as an object. This can be done with the following class definition:
(define-class <point> ()
(x y))
This definition binds the symbol <point>
to a new class whose instances
contain two slots. These slots are called x
an y
and we suppose here that
they contain the coordinates of a 2D point.
Let us define now a circle, as a 2D point and a radius:
(define-class <circle> (<point>)
(radius))
As we can see here, the class <circle>
is constructed by inheriting from the
class <point>
and adding a new slot (the radius
slot).
Instance creation and slot access
Creation of an instance of a previously defined class can
be done with the make
procedure. 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 slots of the
newly created instance. For instance, the following form:
(define c (make <circle>))
creates a new <circle>
object and binds it to the c
Scheme variable.
Accessing the slots of the newly created circle can be done
with the slot-ref
and the slot-set!
primitives. The slot-set!
primitive permits to set the value of an object slot and slot-ref
permits to get its value.
(slot-set! c 'x 10)
(slot-set! c 'y 3)
(slot-ref c 'x) => 10
(slot-ref c 'y) => 3
Using the 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. For instance, the expression:
(describe c)
prints the following information on the standard output:
#[<circle> 81aa1f8] is an an instance of class <circle>.
Slots are:
radius = #[unbound]
x = 10
y = 3
Slot Definition
When specifying a slot, a set of options can be given to the system. Each option is specified with a keyword. For instance,
-
:init-form can be used to supply a default value for the slot.
-
:init-keyword can be used to specify the keyword used for initializing a slot.
-
:getter can be used to define the name of the slot getter
-
:setter can be used to define the name of the slot setter
-
:accessor can be used to define the name of the slot accessor (see below)
To illustrate slot description, we redefine here the <point>
class seen
before. A new definition of this class could be:
(define-class <point> ()
((x :init-form 0 :getter get-x :setter set-x! :init-keyword :x)
(y :init-form 0 :getter get-y :setter set-y! :init-keyword :y)))
With this definition,
-
the
x
andy
slots are set to 0 by default. -
The value of a slot can also be specified by calling
make
with the:x
and:y
keywords. -
Furthermore, the generic functions
get-x
andset-x!
(resp.get-y
andset-y!
) are automatically defined by the system to read and write thex
(resp.y
) slot.
(define p1 (make <point> :x 1 :y 2))
(get-x p1) => 1
(set-x! p1 12)
(get-x p1) => 12
(define p2 (make <point> :x 2))
(get-x p2) => 2
(get-y p2) => 0
Accessors provide an uniform access for reading and writing an object slot.
Writing a slot is done with an extended form of set!
which is
close to the Common Lisp setf
macro. A slot accessor can be
defined with the :accessor
option in the slot
description. Hereafter, is another definition of our
<point>
class, using an accessor:
(define-class <point> ()
((x :init-form 0 :accessor x-of :init-keyword :x)
(y :init-form 0 :accessor y-of :init-keyword :y)))
Using this class definition, reading the x coordinate of the p
point can be
done with:
(x-of p)
and setting it to 100 can be done using the extended set!
(set! (x-of p) 100)
STklos also define
|
Virtual Slots
Suppose that we need slot named area
in circle objects which contain the
area of the circle. One way to do this would be to add the new slot to the
class definition and have an initialisation form for this slot which takes
into account the radius of the circle. The problem with this approach is that
if the radius
slot is changed, we need to change area
(and
vice-versa). This is something which is hard to manage and if we don’t care,
it is easy to have a area
and radius
in an instance which are
"un-synchronized". The virtual slot mechanism avoid this problem.
A virtual slot is a special slot whose value is calculated rather than stored
in an object. The way to read and write such a slot must be given when the
slot is defined with the :slot-ref
and :slot-set!
slot options.
A complete definition of the <circle>
class
using virtual slots could be:
(define-class <circle> (<point>)
((radius :init-form 0 :accessor radius :init-keyword :radius)
(area :allocation :virtual :accessor area
:slot-ref (lambda (o) (let ((r (radius o))) (* 3.14 r r)))
:slot-set! (lambda (o v) (set! (radius o) (sqrt (/ v 3.14)))))))
Here is an example using this definition of <circle>
(define c (make <circle> :radius 1))
(radius c) => 1
(area c) => 3.14
(set! (area x) (* 4 (area x)))
(area c) => 12.56 ;; (i.e. ⒋π)
(radius c) => 2.0
Of course, we can also used the function describe
to visualize
the slots of a given object. Applied to the prvious c
, it prints:
#[<circle> 81b2348] is an an instance of class <circle>.
Slots are:
area = 12.56
radius = 2.0
x = 0
y = 0
8.2.2. Inheritance
Class hierarchy and inheritance of slots
inheritance Inheritance is specified upon class definition. As said in the introduction, STklos supports multiple inheritance. Hereafter are some classes definition:
(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))
Here,
-
A
,B
,C
have a null list of super classes. In this case, the system will replace it by the list which only contains<object>
, the root of all the classes defined bydefine-class
. -
D
,E
, andF
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 <top>
is also shown; this class is the super class
of all Scheme objects. In particular, <top>
is the super class of all
standard Scheme types.
The set of slots of a given class is calculated by "unioning" the slots of all
its super class. For instance, each instance of the class D
defined before
will have three slots (a
, b
and d
). The slots of a class can be obtained
by the class-slots
primitive. For instance,
(class-slots A) => (a)
(class-slots E) => (a e c)
(class-slots F) => (b e c d a f)
The order of slots is not significant. |
Class precedence list
A class may have more than one superclass [1].
With single inheritance (only one superclass), it is easy to order the super classes from most to least specific. This is the rule:
With multiple inheritance, ordering is harder. Suppose we have
(define-class X ()
((x :init-form 1)))
(define-class Y ()
((x :init-form 2)))
(define-class Z (X Y)
(z :init-form 3))
In this case, given Rule 1, the Z
class is more specific than the X
or Y
class for instances of Z
. However, the :init-form
specified in X
and
Y
leads to a problem: which one overrides the other? Or, stated
differently, which is the default initial value of the x
slot of a Z
instance. The rule in STklos, as in CLOS, is that the superclasses listed
earlier are more specific than those listed later. So:
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 x
slot of previous example is 1 since the
class X
is placed before Y
in the super classes of Z
. These two rules
are not always sufficient to determine a unique order. However, they give an
idea of how the things work. STklos algorithm for calculating the class
precedence list of a class is a little simpler than the CLOS one described in
[AMOP] for breaking ties. Consequently, the calculated class
precedence list by STklos algorithm can be different than the one given by
the CLOS one in some subtle situations. Taking the F
class shown in Figure 1,
the STklos calculated class precedence list is
(F D E A B C <object> <top>)
whereas it would be the following list with a CLOS-like algorithm:
(F D E A C B <object> <top>)
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
class-precedence-list
. This function returns a ordered list whose first
element is the most specific class. For instance,
(class-precedence-list D)
=> (#[<class> D 81aebb8] #[<class> A 81aab88]
#[<class> B 81aa720] #[<class> <object> 80eff90]
#[<class> <top> 80effa8])
However, this result is hard to read; using the function
class-name
yields a clearer result:
(map class-name (class-precedence-list D))
=> (D A B <object> <top>)
8.2.3. Generic functions
Generic functions and methods
Neither STklos nor CLOS use the message passing mechanism
for methods as most Object Oriented languages do. Instead, they
use the notion of generic function.A generic function
can be seen as a "tanker" of methods. When the
evaluator requests the application of a generic function, all the
applicable methods of this generic function will be grabbed and
the most specific among them will be applied. We say that a
method M
is more specific than a method M'
if the class of its parameters are more specific than the M'
ones. To be more precise, when a generic function must be
"called" the system
-
searchs among all the generic function methods those which are applicable (i.e. the ones which filter on types which are compatible with the actual argument list),
-
sorts the list of applicable methods in the "most specific" order,
-
calls the most specific method of this list (i.e. the first of the list of sorted methods).
The definition of a generic function is done with the
define-generic
macro. Definition of a new method is
done with the define-method
macro.
Consider the following definitions:
(define-generic M)
(define-method M((a <integer>) b) 'integer)
(define-method M((a <real>) b) 'real)
(define-method M(a b) 'top)
The define-generic
call defines M
as a
generic function. Note that the signature of the generic
function is not given upon definition, contrarily to CLOS. This
permits methods with different signatures for a given generic
function, as we shall see later. The three next lines define
methods for the M
generic function. Each method uses a
sequence of parameter specializers that specify when
the given method is applicable. A specializer permits to
indicate the class a parameter must belong (directly or
indirectly) to be applicable. If no specializer is given, the
system defaults it to <top>
>. Thus, the first method
definition is equivalent to
(define-method M((a <integer>) (b <top>)) 'integer)
Now, let us look at some possible calls to generic
function M
:
(M 2 3) => integer
(M 2 #t) => integer
(M 1.2 'a) => real
(M #t #f) => top
(M 1 2 3) => error (no method with 3 parameters)
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
(define-method M ((a <integer>) (b <number>))
'integer-number)
(define-method M ((a <integer>) (b <real>))
'integer-real)
(define-method M (a (b <number>))
'top-number)
(define-method M (a b c)
'three-parameters)
In this case, we have
(M 1 2) => integer-integer
(M 1 1.0) => integer-real
(M 'a 1) => top-number
(M 1 2 3) => three-parameters
|
Next-method
When a generic function is called, the list of applicable methods is built. As mentioned before, the most specific method of this list is applied (see Section 8.2.3).
This method may call, if needed, the next method in the list of
applicable methods. This is done by using the special form
next-method
. Consider the following definitions
(define-method Test((a <integer>))
(cons 'integer (next-method)))
(define-method Test((a <number>))
(cons 'number (next-method)))
(define-method Test(a)
(list 'top))
With those definitions, we have:
(Test 1) => (integer number top)
(Test 1.0) => (number top)
(Test #t) => (top)
Standard generic functions
Printing objects
When the Scheme primitives write
or display
are called with a
parameter which is an object, the write-object
or display-object
generic functions are called with this object and the port to which
the printing must be done as parameters. This facility permits to
define a customized printing for a class of objects by simply defining
a new method for this class. So, defining a new printing method
overloads the standard printing method (which just prints the class of
the object and its hexadecimal address).
For instance, we can define a customized printing for the <point>
used before as:
(define-method display-object ((p <point>) port)
(format port "<Point x=~S y=~S>" (slot-ref p 'x) (slot-ref p 'y)))
With this definition, we have
(define p (make <point> :x 1 :y 2))
(display p) |= <Point x=1 y=2>
The Scheme primitive write
tries to write objects, in such a way
that they are readable back with the read
primitive. Consequently,
we can define the writing of a <point>
as a form which, when read,
will build back this point:
(define-method write-object ((p <point>) port)
(format port "#,(make <point> :x ~S :y ~S)"
(get-x p) (get-y p)))
With this method, writing the p
point defined before
prints the following text on the output port:
#,(make <point> :x 1 :y 2)
Note here the usage of the #,
notation of SRFI-10 (Sharp Comma External Form) used here
to "evaluate" the form when reading it. We suppose here that we are in a context
where we already defined:
(define-reader-ctor 'make (lambda l (eval `(make ,@l))))
Comparing objects
When objects are compared with the eqv?
or equal?
Scheme standard
primitives, STklos calls the object-eqv?
or object-equal?
generic functions. This facility permits to define a customized
comparison function for a class of objects by simply defining a new
method for this class. Defining a new comparison method overloads the
standard comparaison method (which always returns #f
). For instance
we could define the following method to compare points:
(define-method object-eqv? ((a <point>) (b <point>))
(and (= (point-x a) (point-x b))
(= (point-y a) (point-y b))))
8.3. Object System Main Functions and Syntaxes
8.3.1. Classes and Instances
STklos syntax
Creates a class whose name is name
, and whose superclasses are in the
list supers
, with the slots specified by the list slots
.
As an example, this is the definition of a point:
(define-class <point> ()
(x y))
In another example, a class <circle>
that inherits <point>
.
(define-class <circle> (<point>)
(radius))
The following options can be passed to slots:
-
:init-form
is the default value for the slot. -
:init-keyword
is the keyword for initializing the slot. -
:getter
is the name of the getter method. -
:setter
is the name of the setter method. -
:accessor
is the name of the accessor (setter and getter) method.
For example,
(define-class <point> ()
(x :init-form 0 :getter get-x :setter set-x! :init-keyword :x)
(y :init-form 0 :getter get-y :setter set-y! :init-keyword :y))
STklos also defines setters for the specified getters, so the following
will work with the definition of <point>
given above:
(set! (slot-ref my-point 'x) 50)
Accessors, are methods which can be used as getter and setter, as shown bellow
(define-class <circle> (<point>)
((radius :accessor radius :init-keyword :radius)))
(define x (make <circle> :radius 100))
(radius x) => 100
(set! (radius x) 200)
(radius x) => 200
STklos procedure
Make-instance
creates and initializes an instance of class c
.
Allocate-instance
is similar, but only allocates the instance,
without initializing it.
(define-class <X> () ((xx :init-form 10)))
(define my-x (make-instance <X>))
(describe my-x)
#[<X> 7fbd2e3075d0] is an an instance of class <X>.
The only slot is:
xx = 10
(define my-other-x (allocate-instance <X> 20))
(describe y)
#[<X> 7fbd2e2bdab0] is an an instance of class <X>.
The only slot is:
xx = #[unbound]
make is an alias for make-instance .
|
Initialize
is a generic function that is called by make-instance
(or make
) in order to initialize instances of a class.
(define-class <x> (<object>)
((a :init-keyword :a)
(b :accessor x-b)
c))
(describe (make <x> :a 10))
#[<x> 7f8a49e96bd0] is an instance of class <x>.
Slots are:
a = 10
b = #[unbound]
c = #[unbound]
If an initialize
method is implemented, it overrides the
default, and the initializing arguments are not automatically
processed:
(define-method initialize-instance ((x <x>) args)
(print '== " " args)
(set! (bb x) 1000))
(describe (make <x> :a 10))
== (a 10)
#[<x> 7f8a49e96bd0] is an instance of class <x>.
Slots are:
a = #[unbound]
b = 1000
c = #[unbound]
Note that a
was not initialized! This is because the new
initialize
method does not set it, and the keyword initializer
was ignored (the method actually printed the arguments, but
ignored them).
But if next-method
is called at the end of our initialize
method, the result will be as expected:
(define-method initialize-instance ((x <x>) args)
(print '== " " args)
(set! (bb x) 1000)
(next-method))
(describe (make <x> :a 10))
== (a 10)
#[<x> 7f8a49e96bd0] is an instance of class <x>.
Slots are:
a = 10
b = 1000
c = #[unbound]
Now a
has also been initialized.
initialize is an alias for initialize-instance .
|
Finally, the standard make-instance
is simple to write: it just
calls allocate-instance
and initializes it with initialize-instance
:
(define-method make-instance ((class <class>) . initargs)
(let ((instance (allocate-instance class initargs)))
(initialize-instance instance initargs)
instance))
STklos procedure
Returns the name of the class c
,a s a symbol.
(define-class <A> () ())
(define x (make <A>))
(class-name (class-of x)) => <A>
STklos procedure
Return the class of object obj
.
(define-class <A> () ())
(class-of (make <A>)) => #[<class> <A> 7f5dc5002a20]
(class-of #t) => #[<class> <boolean> 7f5dc551ade0]
STklos procedure
Returns the name of the class c
,a s a symbol.
(define-class <A> () ())
(define x (make <A>))
(class-name (class-of x)) => <A>
STklos procedure
Returns the class whose name is equal to symbol name
. If name
is not
a class instance, the default
value is returned, if present.
STklos procedure
Returns #t
if obj
is an instance of class
, and #f
otherwise.
STklos procedure
Given a list of classes, returns a class which is a superclass of all the classes given. This superclass will be built so that it is a subclass of the most specific classes posible.
(define-class <A> () ())
(define-class <B> (<A>) ())
(define-class <C> () ())
(ensure-metaclass (list <B> <A>)) => #[<class> <B> 7fea2b8a3c30]
(define m (ensure-metaclass (list <B> <A> <C>)))
(class-direct-superclasses m)
=> (#[<class> <B> 7fea2b8a3c30] #[<class> <C> 7fea2b8afb40])
STklos procedure
Change-class
will change the class of object
to new-class
.
The values of slots that do exist in both the old and new classes
are carried out to the new object. The slots that do not exist in
the old instance are initialized as if the object was new.
(define-class <A> () ((a-slot #:init-form 'a-value)))
(define-class <B> () ((b-slot #:init-form 'b-value)))
(define-class <C> (<A> <B>) ((c-slot #:init-form 'c-value)))
(define a (make <A>))
(define c (make <C>))
(describe a)
#[<A> 7f91fe79c660] is an instance of class <A>.
The only slot is:
a-slot = a-value
(describe c)
#[<C> 7f91fe7a0510] is an instance of class <C>.
Slots are:
a-slot = a-value
b-slot = b-value
c-slot = c-value
(change-class a <C>)
(change-class c <A>)
(describe a)
#[<C> 7f91fe79c660] is an instance of class <C>.
Slots are:
a-slot = a-value
b-slot = b-value
c-slot = c-value
(describe c)
#[<A> 7f91fe7a0510] is an instance of class <A>.
The only slot is:
a-slot = a-value
(slot-value c 'c-slot) => error
The last line shows that c
really changed into an object of
class <A>
: it does not have the slot c-slot
anymore.
8.3.2. Generic Functions and Methods
Generic functions and methods are an important part of STklos object system. A generic function is a function which can have several methods with a behavior which depends of the type or the number of its parameters.
STklos procedure
This function creates a new generic function named gf
. By default, the created
function is an instance of the class <generic>
, except if metaclass
is used.
Using a different metaclass permits to fine tune the way methods are applied.
Generally, it is not necessary to use define-generic to declare a generic
function, since it is created the first time a method with the name of that function
is created. However, for the sake of documentation, or to export a generic function
from a module which do not define methods, it is possible also to explicitly
define the generic function before its methods.
|
STklos syntax
The primitive define-method
creates a generic method that will be added to a
generic function gf
, having arguments args
and body body
.
Args
is a list of arguments, and each argument can be either a single
symbol (the argument name) or an argument specification.
In its simplest form, a generic method behaves a lot like an ordinary
procedure:
(define-method square (x)
(* x x))
(square 10) => 100
In the above example, x
is the only argument to the method, and it is
specified as a single symbol, which means that any type of argument will
be matched and this method will be used.
An argument may also be specified in the form of a list with two elements: the argument name and its class. The method will only be applicable when its arguments are of the specified classes (or types).
(define-method ++ ( (x <string>) (y <string>) )
(string-append x y))
(define-method ++ ( (x <number>) (y <number>) )
(+ x y))
(define-method ++ ( (x <string>) (y <number>) )
(format #f (string-append x "~a") y))
(++ "abc" "def") => "abcdef"
(++ 2 3.5) => 5.5
(++ 2 2-3i) => 4-3i
(++ "number: " 10) => "number: 10"
(++ 3 "wrong") => error
In this example, three methods are added to the generic function ++
,
and when the generic function is called, the appropriate method is chosen.
When no method is applicable, an error is signaled.
The classes matched in the example are <string>
and <number>
, but any
class can be used. All Scheme types are built-in classes in STklos:
<boolean> <null>
<char> <object>
<class> <pair>
<complex> <procedure>
<eof> <rational>
<integer> <real>
<fixnum> <bignum>
<list> <symbol>
<vector> ...
User-defined classes can also be used (and this is the main original use case for generic functions). A very simple example follows.
(define-class <figure> ()
((pos-x :getter pos-x :init-keyword :pos-x)
(pos-y :getter pos-y :init-keyword :pos-y)))
(define-class <circle> (<figure>)
((radius :getter radius :init-keyword :radius)))
(define-class <square> (<figure>)
((side :getter side :init-keyword :side)))
(define-method describe-figure ( (c <circle>) )
(format #f "A circle at (~a, ~a), with radius ~a.~%"
(pos-x c)
(pos-y c)
(radius c)))
(define-method describe-figure ( (s <square>) )
(format #f "A square centered at (~a, ~a), with side ~a.~%"
(pos-x s)
(pos-y s)
(side s)))
(define sq (make <square> :pos-x 1 :pos-y 2 :side 10))
(define ci (make <circle> :pos-x 2 :pos-y 2 :radius 5))
(describe-figure sq)
=> "A square centered at (1, 2), with side 10.n"
(describe-figure ci)
=> "A circle at (2, 2), with radius 5.n"
Generic methods can be created with different number of arguments, and that will also be one criterion to match the method.
(define-method process (a b c) (+ a b c))
(define-method process (a b) (* a b))
(process 2 3) => 6
(process 2 3 4) => 9
STklos procedure
Inside a method, one can use the (next-method)
to call the next
applicable method.
(define-method class-list ((obj <integer>)) (cons 'integer (next-method)))
(define-method class-list ((obj <rational>)) (cons 'rational (next-method)))
(define-method class-list ((obj <real>)) (cons 'real (next-method)))
(define-method class-list ((obj <complex>)) (cons 'complex (next-method)))
(define-method class-list ((obj <number>)) (cons 'number (next-method)))
(define-method class-list ((obj <top>)) (list 'top))
(class-list 2.3) => (real complex number top)
(class-list 2) => (integer rational real complex number top)
(class-list 1-2i) => (complex number top)
STklos procedure
Given a method m
,
* method-generic-function
returns the generic function that this
object is a specialization of;
* method-specializers
returns a list of specializers (this and other
methods) of its generic functions;
* method-procedure
returns the procedure that implements this method.
(define-method compute ( (a <real>) (b <real>) )
(* a b))
(define-method compute ( (a <real>) (b <complex>) )
(* a (imag-part b)))
(define m (car (generic-function-methods compute)))
(method-generic-function m)
=> #[<generic> compute (2)]
(method-specializers m)
=> (#[<class> <real> 7f3e3a157ba0]
#[<class> <complex> 7f3e3a157bd0])
(method-procedure m)
=> #[closure 7f9901d6e680]
8.3.3. Misc.
STklos procedure
class-direct-superclasses
and class-direct-subclasses
returns a list with
all direct superclasses or subclasses of class c
.
class-subclasses
returns a list with all subclasses of class c
, even if
not direct.
class-precedence-list
returns the precedence list for class c
. If
c
is a superclass of several classes, this list shows what precedence
each superlclass has when resolving names of attributes and methods in
c
.
The items returned in the list are classes. Use class-name
to obtain
their names.
(define-class <A> () (slot-one slot-two))
(define-class <B> () ())
(define-class <C> (<A> <B>) (slot-one))
(define-class <D> (<A>) ())
(map class-name (class-direct-superclasses <C>)) => (<A> <B>)
(map class-name (class-direct-superclasses <D>)) => (<A>)
(map class-name (class-direct-subclasses <A>)) => (<D> <C>)
(map class-name (class-precedence-list <C>)) => (<C> <A> <B> <object> <top>)
Slot-one
in class <C>
refers to the slot defined in <C>
(because
it is present in both <A>
and <C>
, but <C>
has precedence over
<A>
). Slot-two
in <C>
can only be the one defined in <A>
.
The native STklos types are also subclasses of one single class, <top>
.
(map class-name (class-subclasses <top>))
=> (<entity-class> <procedure-class> <class> <accessor-method>
<simple-method> <method> <generic> <object> <boolean> <char>
...
<number>
...
<output-port> <port> <hash-table>)
One of the listed classes above is <number>
(its subclasses are also
listed in that output). Calling class-subclasses
on <number>
returns
its six subclasses:
(map class-name (class-subclasses <number>))
=> (map class-name (class-subclasses <number>))
STklos procedure
Class-slots
returns a list of the slots in class c
, including
those inherited from other classes.
(class-slots <A>) => ((a #:init-form 1) (b #:init-form 2))
(class-slots <C>) => ((b #:init-form 2) (a #:init-form 3))
Class-direct-slots
returns a list with the names of the slots that
were defined directly in the class (excluding the inherited slots).
(class-direct-slots <B>) => ((a #:init-form 3))
R5RS procedure
Class-direct-methods
returns a list of all direct methods of
a class.
Class-methods
returns a list of all methods of a class and also
of its subclasses.
(define-class <A> () ())
(define-method method-A ((a <A>)) (print 'A))
(define-class <C> (<A>) ())
(define-method method-C ((c <C>)) (print 'C))
(class-methods <C>)
(map method-generic-function (class-methods <A>))
=> (#[<generic> method-C (1)]
#[<generic> method-A (1)])
(map method-generic-function (class-direct-methods <A>))
=> (#[<generic> method-A (1)])
STklos procedure
Class-slot-definition
returns the definition of slot s
in class c
.
(define-class <A> ()
((slot-one :init-form 'none :getter get-one)
(slot-two :init-form 'nada :init-keyword :two)))
(class-slot-definition <A> 'slot-two)
=> (slot-two #:init-form 'nada #:init-keyword #:two)
(class-slots <A>)
=> ((slot-one #:init-form 'none #:getter get-one)
(slot-two #:init-form 'nada #:init-keyword #:two))
STklos procedure
These procedures operate on slot definitions (the lists included in class
definitions). They return the slot name, the list of options, the slot
allocation, and the getter, setter, accessor, init-form and init-keyword
of a slot, as returned by class-slots
or by class-slot-definition
.
(slot-definition-name '(x #:init-form 0 #:accessor x)) => x
(slot-definition-accessor '(x #:init-form 0 #:accessor x)) => x
(slot-definition-allocation '(x #:init-form 0 #:accessor x)) => instance
(slot-definition-options '(x #:init-form 0 #:accessor x))
=> (#:init-form 0 #:accessor x)
STklos procedure
Given a generic function object, these procedures return the name, the list of methods and the documentation of the generic function.
For example,
(define-generic compute :documentation "This function does computations")
(define-method compute ( (a <real>) (b <real>) )
(* a b))
(define-method compute ( (a <real>) (b <complex>) )
(* a (imag-part b)))
(generic-function-name compute) => compute
(generic-function-methods compute) => (#[<method> 7f9901743db0]
#[<method> 7f990155af60])
(generic-function-documentation compute) => "This function does computations"
It is not necessary that the generic function be explicitly defined. If it is
not explicitly created, however, the documentation will be set to #f
.
// SPDX-License-Identifier: GFDL-1.3-or-later // // Copyright © 2000-2023 Erick Gallesio <eg@stklos.net> // // Author: Erick Gallesio [eg@unice.fr] // Creation date: 26-Nov-2000 18:19 (eg)