TinyCLOS

  1. TinyCLOS
  2. Introduction
  3. Defining Forms
    1. define-class
    2. define-generic
    3. define-method
  4. Base Language
    1. add-method
    2. make
    3. make-class
    4. define-class*
    5. make-generic
    6. make-method
    7. slot-set!
    8. slot-ref
    9. slot-value
  5. Introspection
    1. class-cpl
    2. class-direct-slots
    3. class-direct-supers
    4. class-of
    5. class-name
    6. class-slots
    7. generic-methods
    8. method-specializers
    9. method-procedure
    10. instance?
    11. class?
    12. generic?
    13. subclass?
    14. instance-of?
    15. generic-name
  6. Intercessory Protocol
    1. allocate-instance
    2. compute-apply-generic
    3. compute-apply-methods
    4. compute-methods
    5. compute-cpl
    6. compute-getter-and-setter
    7. compute-method-more-specific?
    8. compute-slots
    9. initialize
  7. Additional Protocol
    1. print-object
    2. describe-object
    3. Detail Protocol
      1. Usage
      2. detail-object
  8. Utilities
    1. slot@
    2. initialize-slots
    3. make/copy
  9. Built-in Classes
    1. <object>
    2. <class>
    3. <procedure-class>
    4. <entity-class>
    5. <generic>
    6. <method>
    7. <primitive>
    8. <immediate>
    9. <sequence>
    10. <void>
    11. <null>
    12. <boolean>
    13. <end-of-file>
    14. <char>
    15. <symbol>
    16. <vector>
    17. <list>
    18. <pair>
    19. <number>
    20. <complex>
    21. <real>
    22. <rational>
    23. <integer>
    24. <exact>
    25. <inexact>
    26. <string>
    27. <port>
    28. <input-port>
    29. <output-port>
    30. <environment>
    31. <procedure>
    32. <condition>
    33. <blob>
    34. <structure>
    35. <condition>
    36. <continuation>
    37. <hash-table>
    38. <queue>
    39. <char-set>
    40. <time>
    41. <thread>
    42. <mutex>
    43. <condition-variable>
    44. <u8vector>
    45. <s8vector>
    46. <u16vector>
    47. <s16vector>
    48. <u32vector>
    49. <s32vector>
    50. <f32vector>
    51. <f64vector>
    52. <array>
    53. <lock>
    54. <mmap>
    55. <pointer>
    56. <tagged-pointer>
    57. <swig-pointer>
    58. <locative>
    59. <promise>
    60. <tcp-listener>
    61. <regexp>
    62. <c++-object>
  10. Extending class-of
    1. register-primitive-class-of
    2. Specialized Extensions
      1. add-primitive-class-of
      2. add-structure-class-of
      3. add-tagged-pointer-class-of
      4. add-extended-procedure-class-of
      5. delete-primitive-class-of
      6. delete-structure-class-of
      7. delete-tagged-pointer-class-of
      8. delete-extended-procedure-class-of
  11. Usage
  12. Authors
  13. Version History
  14. License

Introduction

This extension is a port of Gregor Kiczales TinyCLOS with numerous modifications.

You might consider taking a look at coops for a more up-to-date CLOS-like object system for CHICKEN. The tinyclos egg is outdated, please use coops.

Defining Forms

define-class

[syntax] (define-class NAME (SUPERCLASS ...) (SLOT-NAME ...) [METACLASS])

Sets the variable NAME to a new class: a new instance of the class <class> or METACLASS, if supplied.

SUPERCLASS ... is a list of superclasses of the newly created class. If no superclasses are given, then <object> is assumed.

SLOT-NAME ... are the names of the direct slots of the class. A SLOT-NAME is a symbol.

By convention, identifiers bound to classes in TinyCLOS are denoted by angle brackets as above. This is not required, however. A class may be bound to any identifier without affecting the behavior of the system.

(define-class NAME (SUPERCLASS ...) (SLOT-NAME ...) METACLASS)

is equivalent to

(define NAME
  (make METACLASS
        'name 'NAME
        'direct-supers (list SUPERCLASS ...)
        'direct-slots (list 'SLOT-NAME ...)) )

define-generic

[syntax] (define-generic NAME [CLASS])

Binds the variable NAME to a fresh generic function object without associated methods. If the optional argument CLASS is given, then the generic function will be an instance of that class.

The new generic function cannot be called until appropriate methods are defined. To do that, use add-method or define-method.

define-method

[syntax] (define-method (NAME (VARIABLE1 CLASS1) ... ARGUMENT ...) BODY ...)

Adds a new method with the code BODY ... to the generic function bound to NAME.

CLASS1 ... is a list if classes that specialize this particular method.

The method can have additional ARGUMENTS, which do not specialize the method any further. Extended lambda-lists are allowed (argument lists with #!optional, #!key, and/or #!rest), but cannot be specialized.

Inside the BODY ... of the method the identifier call-next-method names a procedure of zero arguments that can be invoked to call the next applicable method with the same arguments.

; Create new generic
(define-generic square-number?)

; Provide a method to operate on integers
(define-method (square-number? (n <integer>))
  (and (positive? n)
    (let ((sq (truncate (sqrt n))))
      (= (* sq sq) n))))

; Provide a method to operate on inexact numbers
(define-method (square-number? (n <inexact>)) #f)

This example also shows that generics can be used without creating classes. It exploits the primitive class system of TinyCLOS, in which all Scheme objects are members of a built-in class hierarchy.

It is an error to use define-method if no generic function is bound to NAME. This is a change from previous versions of TinyCLOS.

Currently methods defined with define-method should not be hidden (via (declare (hide ...)), nor should such files be compiled in block mode, unless the methods are exported.

Base Language

add-method

[procedure] (add-method GENERIC METHOD)

Adds the method object METHOD to the list of applicable methods for the generic function GENERIC.

make

[procedure] (make CLASS INITARG ...)

Creates a new instance of CLASS and passes INITARG ... to the initialize method of this class.

If CLASS is the <primitive> class, the result of this procedure is #<unspecified>.

make-class

[procedure] (make-class SUPERCLASSES SLOT-NAMES)

Creates a new class object, where SUPERCLASSES should be the list of direct superclass objects and SLOT-NAMES is a list of symbols naming the slots of this class.

define-class*

[syntax] (define-class* (SUPERCLASS1 ...) (SLOT-NAME1 ...) [METACLASS])

This macro returns a new anonymous class defined using syntax similar to define-class. As in define-class the superclass and slotname lists are not quoted.

(let ((CLASS (define-class* (SUPER) (SLOT1 SLOT2) METACLASS))) ...)

is equivalent to

(let ((CLASS (make METACLASS (list SUPER) (list 'SLOT1 'SLOT2)))) ...)

and

(let ((CLASS (define-class* () (SLOT1 SLOT2)))) ...)

is equivalent to

(let ((CLASS (make <class> (list <object>) (list 'SLOT1 'SLOT2)))) ...)

make-generic

[procedure] (make-generic [NAME]) => generic

Creates a new generic function object.

NAME is a string.

make-method

[procedure] (make-method SPECIALIZERS PROC)

Creates a new method object specialized to the list of classes in SPECIALIZERS.

(define-method (foo (x <bar>)) 123)
 <=>
(add-method foo
  (make-method
   (list <bar>)
   (lambda (call-next-method x) 123)))

slot-set!

[procedure] (slot-set! INSTANCE SLOT-NAME VALUE)
[setter] (set! (slot-ref INSTANCE SLOT-NAME) VALUE)

Sets the value of the slot SLOT-NAME of the object INSTANCE to VALUE.

Note that a SLOT-NAME is not required to be a symbol, so the following is perfectly valid:

(define hidden-slot (list 'hidden))
(define <myclass>
  (make <class>
        'direct-supers (list <object>)
        'direct-slots (list hidden-slot) ) )
(define x1 (make <myclass>)
(slot-set! x1 hidden-slot 99)

To exploit this, however, the make call has to be used as above. define-class only supports symbolic slot names.

slot-ref

slot-value

[procedure] (slot-ref INSTANCE SLOT-NAME) => *
[procedure] (slot-value INSTANCE SLOT-NAME) => *

Returns the value of the slot SLOT-NAME of the object INSTANCE.

Introspection

class-cpl

[procedure] (class-cpl CLASS) => list

Returns the class-precedence-list of CLASS as a list of classes.

class-direct-slots

[procedure] (class-direct-slots CLASS) => list

Returns the list of direct slots of CLASS as a list of lists, where each sublist contains the name of the slot.

class-direct-supers

[procedure] (class-direct-supers CLASS)

Returns the list of direct superclasses of CLASS.

class-of

[procedure] (class-of X) => class

Returns the class that the object X is an instance of.

See Extensions below for an API to extend the set of "built-in" classes.

class-name

[procedure] (class-name CLASS) => *

Returns name of CLASS.

class-slots

[procedure] (class-slots CLASS) => list

Returns the list of all slots of CLASS and its superclasses as a list of lists, where each sublist contains the name of the slot.

generic-methods

[procedure] (generic-methods GENERIC) => list

Returns the list of all methods associated with the generic function GENERIC.

method-specializers

[procedure] (method-specializers METHOD) => list

Returns the list of classes that specialize METHOD.

method-procedure

[procedure] (method-procedure METHOD) => procedure

Returns the procedure that contains the body of METHOD.

instance?

[procedure] (instance? X) => boolean

Is X an instance of a non-primitive class?

A generic procedure is considered an instance.

class?

[procedure] (class? X) => boolean

Is X an instance of a non-primitive class?

generic?

[procedure] (generic? X) => boolean

Is X a generic procedure?

subclass?

[procedure] (subclass? CLASS1 CLASS2) => boolean

Is CLASS1 a subclass of CLASS2?

Note that the following holds:

(subclass? CLASS CLASS) ;==> #t

instance-of?

[procedure] (instance-of? X CLASS) => boolean

Is X an instance of a subclass of CLASS?

Remember that a class is a subclass of itself.

generic-name

[procedure] (generic-name X) => string

The name of the generic procedure X, or #f.

Intercessory Protocol

These definitions allow interfacing to the Meta Object Protocol of TinyCLOS. For serious use, it is recommended to consult the source code (tinyclos.scm).

allocate-instance

<generic>(allocate-instance CLASS) => instance</generic>

Allocates storage for an instance of CLASS and returns the instance.

compute-apply-generic

<generic>(compute-apply-generic GENERIC) => procedure</generic>

Returns a procedure that will be called to apply the generic function methods to the arguments.

compute-apply-methods

<generic>(compute-apply-methods GENERIC) => procedure</generic>

Returns a procedure of two arguments, a list of applicable methods and a list of arguments and applies the methods.

compute-methods

<generic>(compute-methods GENERIC) => procedure</generic>

Returns a procedure of one argument. The procedure is called with the list of actual arguments passed to the generic function and should return a list of applicable methods, sorted by precedence.

compute-cpl

<generic>(compute-cpl CLASS) => list</generic>

Computes and returns the class-precedence-list of CLASS.

compute-getter-and-setter

<generic>(compute-getter-and-setter CLASS SLOT ALLOCATOR) => procedure procedure</generic>

Returns two values, the procedures that get and set the contents of the slot SLOT. ALLOCATOR is a procedure of one argument that gets an initializer function and returns the getter and setter procedures for the allocated slot.

compute-method-more-specific?

<generic>(compute-method-more-specific? GENERIC) => procedure</generic>

Returns a procedure of three arguments (two methods and a list of arguments) that returns #t if the first method is more specific than the second one with respect to the list of arguments. Otherwise the returned predicate returns #f.

compute-slots

<generic>(compute-slots CLASS) => list</generic>

Computes and returns the list of slots of CLASS.

initialize

<generic>(initialize INSTANCE INITARGS)</generic>

Initializes the object INSTANCE. INITARGS is the list of initialization arguments that were passed to the make procedure.

Note the slot name can only be initialized with a string or symbol, which is coerced to a string. When the supplied name follows the canonical form for class names, <...>, the outer angle brackets are stripped.

Additional Protocol

<generic>(print-object INSTANCE [PORT])</generic>

Writes a terse textual representation of INSTANCE to PORT. Any output of an instance with display, write, and print will invoke this generic function.

If PORT is not given it defaults to the value of (current-output-port).

describe-object

<generic>(describe-object OBJECT [PORT])</generic>

Writes a verbose textual description of OBJECT to PORT.

If PORT is not given it defaults to the value of (current-output-port).

If given an instance, this procedure will identify the class to which the instance belongs and list the names and values of all its slots. If given a primitive, this procedure will identify the primitive class to which the argument belongs.

(define-class <person> () (name age))
(define-class <customer> (<person>) (last-contact-date purchases customer-number))
(define-method (initialize (p <person>) initargs ) (initialize-slots p initargs))
(define p1 (make <customer> 'name "John" 'purchases 12.80 'customer-number 11))
(describe-object p1)

=>

instance of class customer:

 last-contact-date -> #<unspecified>
         purchases -> 12.8
   customer-number -> 11
              name -> "John"
               age -> #<unspecified>
(describe-object (if #f '()))

=>

instance of primitive class void: #<unspecified>

(describe-object <customer>)

=>

class customer (class):

         Slots: (last-contact-date , purchases , customer-number , name , age)
        Supers: (customer , person , object , top)
  Direct Slots: (last-contact-date , purchases , customer-number)
 Direct Supers: (person)

Detail Protocol

Usage

(require-extension detail-object)

detail-object

<generic>(detail-object OBJECT [PORT])</generic>

Writes a verbose textual desription, in SRFI-10 form, of OBJECT to PORT.

If PORT is not given it defaults to the value of (current-output-port).

Except in very limited cases the object detail cannot be reconstituted as the original object. So detail-object violates the spirit of SRFI 10.

Utilities

slot@

[syntax] (slot@ OBJECT SLOT-NAME ... [= VALUE])

Object slots are de-referenced by name left-to-right, with an optional last slot assignment.

(slot@ foo x y)           ;=> (slot-ref (slot-ref foo 'x) 'y)
(slot@ foo x y = "bar")   ;=> (slot-set! (slot-ref foo 'x) 'y "bar")

Symbolic slot-names only.

initialize-slots

[procedure] (initialize-slots INSTANCE INITARGS)

This procedure takes a sequence of alternating slot-names and initialization values in INITARGS and initializes the corresponding slots in INSTANCE.

(define-class <pos> () (x y))

(define-method (initialize (pos <pos>) initargs)
  (call-next-method)
  (initialize-slots pos initargs))

(define p1 (make <pos> 'x 1 'y 2))
(define p2 (make <pos> 'x 3 'y 5))

make/copy

[procedure] (make/copy INSTANCE [INITARGS])

Returns a copy of the object INSTANCE. INITARGS is the list of initialization arguments that will override the INSTANCE copy slot values.

Built-in Classes

The class hierarchy of builtin classes looks like this:

<top>
  <object>
    <class>
      <procedure-class>
        <entity-class>
	        <generic>
      <method>
      <c++-object>
      <primitive-class>
  <primitive>
    <immediate>
      <void>
      <null>
      <boolean>
      <end-of-file>
      <char>
    <symbol>
    <sequence>
      <vector>
      <list>
      <pair>
      <string>
    <number>
      <complex>
        <real>
          <rational>
            <integer>
              <exact>
            <inexact>
    <procedure>               (subclass <procedure-class>)
    <port>
      <input-port>
      <output-port>
    <pointer>
      <tagged-pointer>
      <swig-pointer>
    <locative>
    <blob>
    <structure>
      <array>                 (subclass <sequence>)
      <u8vector>              (subclass <vector>)
      <s8vector>              (subclass <vector>)
      <u16vector>             (subclass <vector>)
      <s16vector>             (subclass <vector>)
      <u32vector>             (subclass <vector>)
      <s32vector>             (subclass <vector>)
      <f32vector>             (subclass <vector>)
      <f64vector>             (subclass <vector>)
      <char-set>              (subclass <sequence>)
      <condition>
      <continuation>
      <environment>           (subclass <sequence>)
      <hash-table>            (subclass <sequence>)
      <lock>
      <mmap>
      <promise>
      <queue>                 (subclass <sequence>)
      <read-table>
      <regexp>
      <tcp-listener>
      <time>
      <thread>
      <mutex>
      <condition-variable>

<object>

The parent class of all objects.

<class>

The parent class of all class objects.

<procedure-class>

The parent class of objects that can be invoked as a procedure.

<entity-class>

The parent class of objects that can be invoked as a procedure and have slots.

<generic>

The parent class of generic function objects.

<method>

The parent class of method objects.

<primitive>

The parent class of the classes of all primitive Scheme objects.

Note that <object> and <primitive> are disjoint.

<immediate>

The parent class of immediate primitives.

<sequence>

The parent class of indexable primitives.

<void>

<null>

<boolean>

<end-of-file>

<char>

<symbol>

<vector>

<list>

<pair>

<number>

<complex>

<real>

<rational>

<integer>

<exact>

<inexact>

<string>

<port>

<input-port>

<output-port>

<environment>

<procedure>

<condition>

<blob>

<structure>

The classes of primitive Scheme and Chicken objects.

<condition>

SRFI 12 condition objects.

<continuation>

Class of continuation objects captured by continuation-capture.

<hash-table>

<queue>

The classes of extended data types provided by the various library units.

<char-set>

<time>

<thread>

<mutex>

<condition-variable>

<u8vector>

<s8vector>

<u16vector>

<s16vector>

<u32vector>

<s32vector>

<f32vector>

<f64vector>

The classes of data objects provided by the various supplied SRFIs.

<array>

The SRFI 25 and SRFI 63 (array-lib), but not SRFI 47, object.

<lock>

<mmap>

Classes of objects used in the Unit posix.

<pointer>

<tagged-pointer>

<swig-pointer>

A machine pointer (untagged, tagged or pointing to SWIG-wrapped data).

<locative>

A locative.

<promise>

The class of objects returned by delay.

<tcp-listener>

The class of an object returned by tcp-listen.

<regexp>

The class of an object returned by regexp.

<c++-object>

The class of generated wrappers for C++ classes parsed by the "easy" foreign function interface easyffi.

Extending class-of

The class-of procedure can be extended to recognize additional classes beyond the built-in set.

register-primitive-class-of

[procedure] (register-primitive-class-of FUNCTION) => (union #f class)

FUNCTION is a (procedure (*) *) taking a single argument, the object to test, and returning a class when the object is known or #f otherwise.

Specialized Extensions

When extending class-of with an opaque type and the underlying representation is known, it is best not to exploit this knowledge. For example:

This will work but it is brittle:

(use lookup-table)

(define-class <dict> (<structure>) () <primitive-class>)
(add-structure-class-of <dict> 'dict)

Better:

(use lookup-table)

(define-class <dict> () () <primitive-class>)
(add-primitive-class-of <dict> dict?)

add-primitive-class-of

add-structure-class-of

add-tagged-pointer-class-of

add-extended-procedure-class-of

[procedure] (add-primitive-class-of CLASS PREDICATE)
[procedure] (add-structure-class-of CLASS TAG)
[procedure] (add-tagged-pointer-class-of CLASS TAG)
[procedure] (add-extended-procedure-class-of CLASS PREDICATE)

Extends class-of with the supplied CLASS and identity detection.

Any existing entry with matching PREDICATE or TAG will be replaced!

Does not verify the types of its' arguments.

delete-primitive-class-of

delete-structure-class-of

delete-tagged-pointer-class-of

delete-extended-procedure-class-of

[procedure] (delete-primitive-class-of CLASS)
[procedure] (delete-structure-class-of CLASS)
[procedure] (delete-tagged-pointer-class-of CLASS)
[procedure] (delete-extended-procedure-class-of CLASS)

Just removes the supplied CLASS from class-of, if it exists.

Does not verify the types of its' arguments.

Usage

(require-extension tinyclos>

Authors

Original version by Gregor Kiczales, CHICKEN port and performance enhancements by felix winkelmann, some of which have been inspired by Eli Barzilays Swindle

slot@ from the '@' macro by Dan Muresan.

Version History

1.8.6
Removed buggy tests.
1.8.5
1.8.4
Removed call to broken randomize function in CHICKEN 4.6.0 [thanks to Matt Welland]
1.8.3
Removed backwards-incompatible declaration
1.8.2
Printing of '(allocate-instance <top>/<primitive>) handled. [kon lovett]
1.8.1
Fixed <sequence> class-name. Fixed class-of extension implementation type precedence. Name of <generic> is now a string, same as others. [kon lovett]
1.8.0
Added register-primitive-class-of. [kon lovett]
1.7.0
Removed the class-of extension convenience API (It was unused). Fixed <port> & <procedure> subclasses. Added more primitive classes. Name of <class> is now a string, same as others. Added some tagging primitive classes. Added detail-object. Better describe-object. [kon lovett]
1.6.5
Fix for extension of extended-procedure class-of. [kon lovett]
1.6.4
exposed in repository
1.6.0
define-generic now mandatory
1.5
fixed use of obsolete reader hack
1.404
added make/copy [kon lovett]
1.403
added unbound error to class-of [kon lovett]
1.402
fixed bug in class-of that caused crash when passed void [felix winkelmann]
1.401
Uses ':optional' (for older Chicken) [kon lovett]
1.4
Added class-of extension api, and slot@. Much internal re-ordering [kon lovett]
1.3
removed unnecessary feature-registration [thanks to Matthew Welland]
1.2
removed use of :optional in tinyclos.scm [thanks to Todd Ingalls]
1.1
added missing syntax indicator in setup script
1.0
moved from base distribution into separate extension

License

Copyright (c) 2000-2007, Felix L. Winkelmann All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice, this list of conditions and the following
  disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
  disclaimer in the documentation and/or other materials provided with the distribution.
Neither the name of the author nor the names of its contributors may be used to endorse or promote
  products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.