- Instance creation
- Generic procedures
- Slot access
- Predefined classes
- Predefined generic procedures
- Primitive classes
- Bugs and limitations
- Version History
COOPS is an object system for Scheme that provides
- classes with multiple inheritance;
- generic procedures;
- methods that can specialize on one or more arguments (multimethods);
- before, after, and around auxiliary methods in addition to primary methods;
- call-next-method and next-method? in primary and around methods; and
- standard method combination (the default "demon" protocol used in CLOS).
- SRFI-17 setters as generic procedures
COOPS provides classes, generic functions and methods, similar in style and use to the classic Lisp object systems like Flavors, Loops or CLOS. For general information about object-oriented programming in the context of Lisp, consult one of the various books and guides available to the subject.
define-class[syntax] (define-class CLASSNAME [(SUPERCLASS ...) [(SLOTSPEC ...) CLASSOPTION ...]])
Defines a COOPS class and assigns it to the variable CLASSNAME (which should be a symbol). (SUPERCLASS ...) is a list of expressions evaluating to classes from which the newly defined class should inherit. If no superclasses are given or the superclass list is empty, then <standard-object> is assumed to be the default superclass.
SLOTSPEC specifies a slot (commonly called an instance variable) and should be either a symbol naming the slot or a list of the form (SLOTNAME SLOTOPTION1 OPTIONVALUE1 ...). The syntax (SLOTNAME INITFORM) is also a valid SLOTSPEC and is equivalent to (SLOTNAME initform: INITFORM).
An instance always contains all the slots of all superclasses in addition to its own slots.
Classes are first-class values and are actually instances themselves, of the class <standard-class> (see below for more details).
[slot option] reader: NAME
Defines a method on the generic procedure NAME that takes an instance of the defined class as its sole argument and returns the value of the instance slot with the same name. This is equivalent to
(define-method (NAME (obj CLASSNAME)) (slot-value obj 'NAME))
[slot option] writer: NAME
Defines a method on the generic procedure NAME that takes as arguments an instance of the defined class argument and a value and assigns the value to the instance slot with the same name. This is equivalent to
(define-method (NAME (obj CLASSNAME) val) (set! (slot-value obj 'NAME) val))
[slot option] accessor: NAME
(begin (define-method (NAME (obj CLASSNAME)) (slot-value obj 'NAME)) (define-method ((setter NAME) (obj CLASSNAME) val) (set! (slot-value obj 'NAME) val)))
So, we are defining a generic procedure method for accessing the slot, together with a setter that allows assigning new values to the slot with the syntax
(set! (NAME obj) val)
[slot option] initform: EXPRESSION
If an instance of the defined class is created and this slot has not been given an initialization argument, then EXPRESSION will be evaluated and the result assigned to the slot.
[class option] metaclass: CLASS
The class of which the newly defined class should be an instance (classes are instances, too). The default meta-class is <standard-class>. Use this option if you feel adventurous.
make-class[syntax] (make-class [CLASSNAME] (SUPERCLASS ...) [(SLOTNAME ...) [METACLASS]])
Defines a class. define-class is syntactic sugar around this form and is usually preferred. make-class allows the creation of anonymous (that is: unnamed) classes.
Note that make-class is syntax, not a procedure.
class-name[procedure] (class-name CLASS)
Returns the name of CLASS, if it has one, or #f it not.
subclass?[procedure] (subclass? CLASS1 CLASS2)
Returns #t if CLASS1 is equal to CLASS2 or if it is a subclass of CLASS2 or #f otherwise.
make[procedure] (make CLASS SLOTNAME1 INITFORM1 ...)
Creates an instance of the CLASS and initializes the slots given in the remaining arguments. The new instance is returned. Slots not given which have been declared to have an initform: will be initialized by evaluating that form. All other slots will be uninitialized.
class-of[procedure] (class-of X)
Returns the class of X or #t if X is not a class instance.
initialize-instance[procedure] (initialize-instance OBJECT)
A generic procedure that is automatically invoked after a call to make and which initializes the remaining slots of OBJECT to initforms given in the class definition. If you just want to use this as a constructor, (call-next-method) can be used to initialize the slots.
Generic procedures are like normal procedures but contain a hidden reference to a generic procedure object that holds additional information like method-tables, etc. You can define generic procedures explicitly with the define-generic and make-generic-procedure syntactic forms or implicitly by using define-method.
slot-value and class-of can be used on generic procedures and will transparently access the generic procedure object
generic-procedure?[procedure] (generic-procedure? X)
Returns #t if X is a generic procedure or #f otherwise.
define-generic[syntax] (define-generic (NAME ARGUMENT ...))
Defines a generic procedure, a procedure specialized for one or more argument types. ARGUMENT ... defines the number of specialized arguments this generic procedure shoud use to dispatch to the correct method. The generic procedure may receive additional arguments, but those will not be used to determine the method. This form is roughly equivalent to
(define NAME (make-generic-procedure ARGUMENT ...))
You can use the syntax
(define-generic ((setter NAME) ARGUMENT ...))
to define a SRFI-17 setter on NAME that is itself a generic procedure.
define-method[syntax] (define-method (NAME [QUALIFIER] [(ARGUMENT1 CLASS1) ...] ...) BODY ...)
Defines a method specialized for arguments of the classes CLASS1 ... on the generic procedure NAME. If NAME holds a method for the same argument classes, the previously defined method is replaced.
If no generic procedure has previously been defined for NAME, then a generic procedure definition is done implicitly. Whether it can be assumed a definition exists is assumed to be the case when one of the following holds true:
- NAME is an imported value binding
- a generic procedure has been defined with (define-generic NAME ...) in the same compilation unit or the same interpreter session and is lexically visible
(define-method ((setter NAME) ...) BODY ...)
is allowed and supported.
QUALIFIER may be one of the keywords primary:, before:, after: or around: and mark the method as being either a primary (default) method, a method that is called before or after the primary method or a method that is "wrapped" around more specific methods. before: methods are invoked from most specific to least specific. after: methods are invoked from least specific to most specific. around: methods can chose to invoke the next most specific method with the same arguments by calling (call-next-method) with no arguments.
All arguments of the form (ARGUMENT CLASS) are specialized up to the first occurrence of a plain symbol or until a "rest"-argument or extended lambda-list marker (#!rest, #!optional or #!key) is encountered.
make-generic-procedure[syntax] (make-generic-procedure ARGUMENT ...)
Creates a generic procedure, a procedure decorated with a hidden generic procedure object of class <generic-procedure>. ARGUMENT ... is the list of specialized arguments this procedure should receive. Methods for this generic procedure may accept more required or optional arguments, but the number of specialized arguments must be the same.
Note that make-generic-procedure is syntax, not a procedure.
slot-value[procedure] (slot-value OBJECT SLOTNAME)
Returns the slot named SLOTNAME of the class instance OBJECT, signalling an error if no such slot exists.
(set! (slot-value OBJECT SLOTNAME) VAL)
can be used to assign a value to a slot.
slot-initialized?[procedure] (slot-initialized? OBJECT SLOTNAME)
Returns #t if OBJECT has a slot named SLOTNAME or #f ortherwise.
The base class of classes defined with define-class.
The class of classes (classes are class instances themselves). This implies that <standard-class> is an instance of itself.
A subclass of <procedure> that is the class of generic procedure objects.
The superclass of all other classes.
Predefined generic procedures
print-object[procedure] (print-object OBJECT PORT)
A generic procedure that is invoked when OBJECT is printed.
Primitive classes are classes representing primitive data objects like numbers, strings and record structures. To be able to define generic procedures specialized on these types, primitive classes can be defined and associated with a predicate that returns a true value for objects that should be considered of the type represented by the class.
define-primitive-class[syntax] (define-primitive-class NAME [(SUPERCLASS ...)] PREDICATE)
Defines a primitive class with the name NAME and the given list of superclasses. If no superclasses are specified, then the superclass list defaults to (<primitive-object>). PREDICATE should be a procedure of one argument determining whether the argument is a member of the newly defined primitive class.
The predicate should be referentially transparent.
Predefined primitive classes
The extension coops-primitive-objects defines additional classes that allow defining generic procedures on objects used in the CHICKEN core libraries.[class] <primitive-object>
This is the base class of all primitive object classes. Load the extension coops-primitive-objects to pull in <primitive-object> and its derivatives.
Other classes deriving from <primitive-object>:
|<immediate>||Any immediate object||<primitive-object>|
|<boolean>||#t or #f||<immediate>|
|<eof-object>||end of file||<immediate>|
|<null>||The empty list||<immediate> <list>|
|<number-vector>||SRFI-4 vectors||<sequence> <record>|
|<char-set>||SRFI-13 char sets||<sequence> <record>|
|<condition-variable>||SRFI-18 condition variables||<record>|
|<promise>||created with delay||<record>|
Bugs and limitations
- define-generic, define-method and make-generic-procedure do currently not check for argument-list congruence, so it is important to make sure that the number of specialized arguments is always correct for the given generic procedure.
A simple class:
(define-class <stack> () ((content '()))) ; or "(content initform: '())" (define-method (push (val #t) (stack <stack>)) (set! (slot-value stack 'content) (cons val (slot-value stack 'content)))) (define-method (pop (stack <stack>)) (let* ((c (slot-value stack 'content)) (x (car c))) (set! (slot-value stack 'content) (cdr c)) x)) (define-method (empty? (stack <stack>)) (null? (slot-value stack 'content)))
A subclass of <stack>, with logging:
(define-class <stack-with-logging> (<stack>) ((logfile initform: (current-output-port) accessor: stack-logfile))) (define-method (push before: (val #t) (stack <stack-with-logging>)) (with-output-to-port (stack-logfile stack) ; uses accessor method (lambda () (print "stack: pushing " val)))) (define-method (pop before: (stack <stack-with-logging>)) (with-output-to-port (stack-logfile stack) (lambda () (print "stack: popping " (car (slot-value stack 'content)))))) (define-method ((setter stack-logfile) before: (stack <stack-with-logging>) file) (print "stack: setting logfile to " file))
An example of a custom initialize-instance with call-next-method:
(define-class <c> () ((type initform: 'int reader: type)) ((content))) (define-method (initialize-instance (c <c>)) (call-next-method) (set! (slot-value c 'content) (if (eq? (type c) 'int) 0 "")))
Copyright (c) 1996, Dorai Sitaram 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.
- fixed missing compile-time load of srfi-1 (thanks to Moritz Heidkamp)
- fixed call to test-exit in test script
- added call to test-exit in test script
- made <list> an abstract type, since the type-predicates for primitive classes were not disjoint (thanks to Sandro)
- syntax needed to import srfi-1 (thanks to Moritz Heidkamp)
- modified to use include to handle changes in module syntax (sjamaan)
- made <pair> and <null> subclasses of <list>
- proper initform-handling for all classes
- metaclass-related bugfixes (thanks to Peter Lane)
- fixed bug related to initforms and inheritance
- added type-check in internal slot-lookup procedure (thanks to Kon Lovett)
- removed declaration which is broken on older CHICKENs (4.5.1)
- added type-check in make (thanks to Peter Lane)
- added <integer> class to coops-primitive-objects
- added license to .meta file
- performance tweaks, fix for 4.5.2 dependency (thanks to Mario)
- initial release