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,

  1. the x and y slots are set to 0 by default.

  2. The value of a slot can also be specified by calling make with the :x and :y keywords.

  3. Furthermore, the generic functions get-x and set-x! (resp. get-y and set-y!) are automatically defined by the system to read and write the x (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 slot-set! as the setter function of slot-ref. As a consequence, we have

(set! (slot-ref p 'y) 100)
(slot-ref p 'y)       => 100
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 by define-class.

  • D, E, and F use multiple inheritance: each class inherits from two previously defined classes. Those class definitions define a hierarchy which is shown in Figure 1.

hierarchy
Figure 1. A class hiearchy

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:

Rule 1: Each class is more specific than its superclasses.

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:

Rule 2: For a given class, superclasses listed earlier are more specific than those listed later.

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
  • Before defining a new generic function define-generic, verifies if the symbol given as parameter is already bound to a procedure in the current environment. If so, this procedure is added, as a method to the newly created generic function. For instance:

    (define-generic log)  ; transform "log" in a generic function
    
    (define-method log ((s <string>) . l)
       (apply format  (current-error-port) s l)
       (newline (current-error-port)))
    
    (log "Hello, ~a" "world")      |- Hello, world
    (log 1)                        => 0 ; standard "log" procedure
  • define-method automatically defines the generic function if it has not been defined before. Consequently, most of the time, the define-generic is not needed.

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

(define-class name supers slots . options)

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

(allocate-instance c init-args)
(initialize-instance c init-args)
(make-instance c)
(initialize c init-args)
(make c)

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

(class-name c)

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

(class-of obj)

Return the class of object obj.

(define-class <A> () ())
(class-of (make <A>))     => #[<class> <A> 7f5dc5002a20]
(class-of #t)             => #[<class> <boolean> 7f5dc551ade0]

STklos procedure

(class-name c)

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

(find-class name)
(find-class name default)

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

(is-a? obj class)

Returns #t if obj is an instance of class, and #f otherwise.

STklos procedure

(ensure-metaclass class-list)

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 object new-class)

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

(define-generic gf)
(define-generic gf metaclass)
(define-generic gf :documentation str)
(define-generic gf metaclass :documentation str)

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

(define-method gf args body)

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

(next-method)

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

(method-generic-function m)
(method-specializers m)
(method-procedure m)

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 c)
(class-direct-subclasses c)
(class-subclasses c)
(class-precedence-list c)

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 c)
(class-direct-slots c)

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-methods c)
(class-direct-methods c)

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 c s)

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

(slot-definition-name s)
(slot-definition-options s)
(slot-definition-allocation s)
(slot-definition-getter s)
(slot-definition-setter s)
(slot-definition-accessor s)
(slot-definition-init-form s)
(slot-definition-init-keyword s)

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

(generic-function-name gf)
(generic-function-methods gf)
(generic-function-documentation gf)

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)

1. Documentation about hygienic macros has been stolen in the SLIB manual
1. In fact define-module on a given name defines a new module only the first time it is invoked on this name. By this way, interactively reloading a module does not define a new entity, and the other modules which use it are not altered.
2. This transcript uses the default toplevel loop which displays the name of the current module in the evaluator prompt.
1. Under Unix, you can simply connect to a listening socket with the telnet of netcat command. For the given example, this can be achieved with netcat localhost 12345
2. Port 13, if open, can be used for testing: making a connection to it permits to know the distant system’s idea of the time of day.
1. The "pattern matching compiler" has been written by Jean-Marie Geffroy and is part of the Manuel Serrano’s Bigloo compiler since several years [Bigloo]
1. This section is an adaptation of Jeff Dalton’s (J.Dalton@ed.ac.uk) "Brief introduction to CLOS" which can be found at http://www.aiai.ed.ac.uk/~jeff/clos-guide.html