;;; Heavily hacked by Eli Barzilay: Maze is Life! (eli@barzilay.org) ;;> This module is the core object system. It is a heavily hacked version ;;> of the original Tiny-CLOS code from Xerox, but it has been fitted to ;;> MzScheme, optimized and extended. See the source file for a lot of ;;> details about how the CLOS magic is created. ;;> ;;> [There is one difference between Swindle and Tiny-CLOS: the meta object ;;> hierarchy is assumed to be using only single inheritance, or if there is ;;> multiple inheritance then the built in meta objects should come first to ;;> make the slots allocated in the same place. This should not be a ;;> problem in realistic situations.] ;;; Original copyright: ;;; *************************************************************************** ;;; Copyright (c) 1992 Xerox Corporation. All Rights Reserved. ;;; ;;; Use, reproduction, and preparation of derivative works are permitted. Any ;;; copy of this software or of any derivative work must include the above ;;; copyright notice of Xerox Corporation, this paragraph and the one after it. ;;; Any distribution of this software or derivative works must comply with all ;;; applicable United States export control laws. ;;; This software is made available AS IS, and XEROX CORPORATION DISCLAIMS ALL ;;; WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED ;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND ;;; NOTWITHSTANDING ANY OTHER PROVISION CONTAINED HEREIN, ANY LIABILITY FOR ;;; DAMAGES RESULTING FROM THE SOFTWARE OR ITS USE IS EXPRESSLY DISCLAIMED, ;;; WHETHER ARISING IN CONTRACT, TORT (INCLUDING NEGLIGENCE) OR STRICT ;;; LIABILITY, EVEN IF XEROX CORPORATION IS ADVISED OF THE POSSIBILITY OF SUCH ;;; DAMAGES. ;;; *************************************************************************** (module tiny-clos (lib "base.ss" "swindle") ;;; A very simple CLOS-like language, embedded in Scheme, with a simple MOP. ;;; The features of the default base language are: ;;; * Classes, with instance slots, but no slot options. ;;; * Multiple-inheritance. ;;; * Generic functions with multi-methods and class specializers only. ;;; * Primary methods and call-next-method; no other method combination. ;;; * Uses Scheme's lexical scoping facilities as the class and generic ;;; function naming mechanism. Another way of saying this is that class, ;;; generic function and methods are first-class (meta)objects. ;;; ;;; While the MOP is simple, it is essentially equal in power to both MOPs in ;;; AMOP. This implementation is not at all optimized, but the MOP is designed ;;; so that it can be optimized. In fact, this MOP allows better optimization ;;; of slot access extenstions than those in AMOP. ;;; ;;; In addition to calling a generic, the entry points to the default base ;;; language are: ;;; ;;; (MAKE-CLASS list-of-superclasses list-of-slot-names) ;;; (MAKE-GENERIC-FUNCTION) ;;; (MAKE-METHOD list-of-specializers procedure) ;;; (ADD-METHOD generic method) ;;; ;;; (MAKE class . initargs) ;;; (INITIALIZE instance initargs) ; Add methods to this, dont call directly. ;;; ;;; (SLOT-REF object slot-name) ;;; (SLOT-SET! object slot-name new-value) ;;; (SLOT-BOUND? object slot-name) ;;; ;;; So, for example, one might do: ;;; (define (make-class (list ) (list 'x 'y))) ;;; (add-method initialize ;;; (make-method (list ) ;;; (lambda (call-next-method pos initargs) ;;; (for-each (lambda (initarg-name slot-name) ;;; (slot-set! pos slot-name ;;; (getarg initargs initarg-name 0))) ;;; '(x y) ;;; '(x y))))) ;;; (set! p1 (make 'x 1 'y 3)) ;;; ;;; NOTE! Do not use EQUAL? to compare objects! Use EQ? or some hand written ;;; procedure. Objects have a pointer to their class, and classes are ;;; circular structures, and... ;;; ;;; The introspective part of the MOP looks like the following. Note that ;;; these are ordinary procedures, not generics. ;;; * CLASS-OF ;;; INSTANCE-OF? ;;; SUBCLASS? ;;; * CLASS-DIRECT-SUPERS ;;; CLASS-DIRECT-SLOTS ;;; CLASS-CPL ;;; CLASS-SLOTS ;;; CLASS-NAME ;;; * GENERIC-METHODS ;;; GENERIC-ARITY ;;; GENERIC-NAME ;;; GENERIC-COMBINATION ;;; * METHOD-SPECIALIZERS ;;; METHOD-PROCEDURE ;;; METHOD-NAME ;;; ;;; The intercessory protocol looks like (generics in uppercase): ;;; ELI: All of these are generic functions now! ;;; MAKE ;;; ALLOCATE-INSTANCE ;;; INITIALIZE (really a base-level generic) ;;; class initialization ;;; COMPUTE-CPL ;;; COMPUTE-SLOTS ;;; COMPUTE-GETTER-AND-SETTER ;;; method initialization ;;; COMPUTE-APPLY-METHOD ;;; ADD-METHOD (Notice this is not a generic!) [eli: yes!] ;;; COMPUTE-APPLY-GENERIC ;;; COMPUTE-METHODS ;;; COMPUTE-METHOD-MORE-SPECIFIC? ;;; COMPUTE-APPLY-METHODS ;;; OK, now let's get going. But, as usual, before we can do anything ;;; interesting, we have to muck around for a bit first. First, we need to ;;; load the support library. [-- replaced with a module.] (require (lib "misc.ss" "swindle")) ;; This is a convenient function for raising exceptions (define (raise* exn-maker fmt . args) (let ([sym (and (symbol? fmt) (begin0 fmt (when (null? args) (error 'raise* "got too few arguments")) (set! fmt (car args)) (set! args (cdr args))))] [fmt-num (- (length args) (procedure-arity exn-maker) -2)]) (when (< fmt-num 0) (error 'raise* "got too few arguments")) (let loop ([fmt-args '()] [args args] [a fmt-num]) (if (zero? a) (raise (exn-maker (if sym (apply format (concat "~s: " fmt) sym (reverse! fmt-args)) (apply format fmt (reverse! fmt-args))) (current-continuation-marks) . args)) (loop (cons (car args) fmt-args) (cdr args) (sub1 a)))))) ;; A simple topological sort. ;; It's in this file so that both TinyClos and Objects can use it. ;; This is a fairly modified version of code I originally got from Anurag ;; Mendhekar . (define (compute-std-cpl c get-direct-supers) (top-sort (build-transitive-closure get-direct-supers c) (build-constraints get-direct-supers c) (std-tie-breaker get-direct-supers))) (define (top-sort elements constraints tie-breaker) (let loop ([elements elements] [constraints constraints] [result '()]) (if (null? elements) result (let ([can-go-in-now (filter (lambda (x) (every (lambda (constraint) (or (not (eq? (cadr constraint) x)) (memq (car constraint) result))) constraints)) elements)]) (if (null? can-go-in-now) (error 'top-sort "invalid constraints") (let ([choice (if (null? (cdr can-go-in-now)) (car can-go-in-now) (tie-breaker result can-go-in-now))]) (loop (filter (lambda (x) (not (eq? x choice))) elements) constraints (append result (list choice))))))))) (define (std-tie-breaker get-supers) (lambda (partial-cpl min-elts) (let loop ([pcpl (reverse partial-cpl)]) (let* ([current-elt (car pcpl)] [ds-of-ce (get-supers current-elt)] [common (filter (lambda (x) (memq x ds-of-ce)) min-elts)]) (if (null? common) (if (null? (cdr pcpl)) (error 'std-tie-breaker "nothing valid") (loop (cdr pcpl))) (car common)))))) (define (build-transitive-closure get-follow-ons x) (let track ([result '()] [pending (list x)]) (if (null? pending) result (let ([next (car pending)]) (if (memq next result) (track result (cdr pending)) (track (cons next result) (append (get-follow-ons next) (cdr pending)))))))) (define (build-constraints get-follow-ons x) (let loop ([elements (build-transitive-closure get-follow-ons x)] [this-one '()] [result '()]) (if (or (null? this-one) (null? (cdr this-one))) (if (null? elements) result (loop (cdr elements) (cons (car elements) (get-follow-ons (car elements))) result)) (loop elements (cdr this-one) (cons (list (car this-one) (cadr this-one)) result))))) ;;; Then, we need to build what, in a more real implementation, would be the ;;; interface to the memory subsystem: instances and entities. The former are ;;; used for instances of instances of ; the latter are used for ;;; instances of instances of . In this MOP, none of this is ;;; visible to base- or MOP-level programmers. ;;; A few things to note, that have influenced the way all this is done: ;;; - R4RS doesn't provide a mechanism for specializing the ;;; behavior of the printer for certain objects. ;;; - Some Scheme implementations bomb when printing circular structures -- ;;; that is, arrays and/or lists that somehow point back to themselves. ;;; So, the natural implementation of instances -- vectors whose first field ;;; point to the class -- is straight on out. Instead, we use a procedure to ;;; `encapsulate' that natural representation. ;;; Having gone that far, it makes things simpler to unify the way normal ;;; instances and entities are handled, at least in the lower levels of the ;;; system. Don't get faked out by this -- the user shouldn't think of normal ;;; instances as being procedures, they aren't. (At least not in this ;;; language.) If you are using this to teach, you probably want to hide the ;;; implementation of instances and entities from people. ;;>> ??? ;;> This is MzScheme's `unspecified' value which is used as the default ;;> value for unbound slots. It is provided so you can check if a slot is ;;> unbound. (define* ??? (letrec ([x x]) x)) ; this is MzScheme's # value (define unspecified-initializer (lambda args ???)) (define false-func (lambda args #f)) ;; Basic allocation follows, all was in a single let, but this is not needed ;; with MzScheme's modules. Also modified to use simple structs for ;; everything, including entities since PLT has applicable struct objects. (define-values (struct:instance make-instance instance? inst-ref inst-set!) ;; slots: applicable, class, function, slots-vector (make-struct-type 'swindleobj #f 3 0 #f '() #f (lambda (o . args) (apply (instance-proc o) args)))) (defsubst (instance-class x) (inst-ref x 0)) (defsubst (instance-proc x) (inst-ref x 1)) (defsubst (instance-slots x) (inst-ref x 2)) (defsubst (set-instance-class! x c) (inst-set! x 0 c)) (defsubst (set-instance-proc! x p) (inst-set! x 1 p)) (defsubst (set-instance-slots! x s) (inst-set! x 2 s)) (defsubst (%instance-ref o f) (vector-ref (instance-slots o) f)) (defsubst (%instance-set! o f n) (vector-set! (instance-slots o) f n)) (define (%allocate-instance class nfields) (make-instance class (lambda args (error 'instance "an instance isn't a procedure -- can't apply it")) (make-vector nfields ???))) (define (%allocate-entity class nfields) (letrec ([o (make-instance class (lambda args (error 'entity "tried to call an entity before its proc is set")) (make-vector nfields ???))]) o)) ;; This is used only once as part of bootstrapping the braid. (define (set-instance-class-to-self! class) (set-instance-class! class class)) ;;>>... ;;> *** Low level functionality ;;> (These functions should be used with caution, since they make shooting ;;> legs in exotic ways extremely easy.) ;;>> (change-class! object new-class initargs ...) ;;> This operation changes the class of the given `object' to the given ;;> `new-class'. The way this is done is by creating a fresh instance of ;;> `new-class', then copying all slot values from `object' to the new ;;> instance for all shared slot names. Finally, the new instance's set ;;> of slots is used for the original object with the new class, so it ;;> preserves its identity. (define* (change-class! obj new-class . initargs) (let ([new (make new-class . initargs)] [new-slots (%class-slots new-class)]) (dolist [slot (%class-slots (class-of obj))] (when (and (not (eq? :class (getarg (cdr slot) :allocation :instance))) (assq (car slot) new-slots)) (slot-set! new (car slot) (slot-ref obj (car slot))))) (set-instance-slots! obj (instance-slots new)) (set-instance-class! obj new-class))) ;; This might be cute for some ugly hacks but not needed for now. ;; Copies the contents of source to target, making it an "alias" object. This ;; is no re-provided by clos.ss, but maybe it will in the future... ;; (define* (copy-object-contents! target source) ;; (set-instance-class! target (instance-class source)) ;; (set-instance-proc! target (instance-proc source)) ;; (set-instance-slots! target (instance-slots source))) ;;>> (set-instance-proc! object proc) ;;> This function sets the procedure of an entity object. It is useful ;;> only for making new entity classes. (provide set-instance-proc!) ; dangerous! ;; Basic allocation ends here. ;;>>... ;;> *** Basic functionality ;;>> (instance? x) ;;>> (object? x) ;;> These two are synonyms: a predicate that returns #t for objects that ;;> are allocated and managed by Swindle. (provide instance?) (define* object? instance?) ;;>> (class-of x) ;;> Return the class object of `x'. This will either be a Swindle class ;;> for objects, or a built-in class for other Scheme values. ;;; %allocate-instance, %allocate-entity, %instance-ref, %instance-set! and ;;; class-of are the normal interface, from the rest of the code, to the ;;; low-level memory system. One thing to take note of is that the protocol ;;; does not allow the user to add low-level instance representations. I have ;;; never seen a way to make that work. ;;; Note that this implementation of class-of assumes the name of a the ;;; primitive classes that are set up later. (define* (class-of x) ;; This is an early version that will be modified when built-in types are ;; introduced later. (if (instance? x) (instance-class x) )) ;;; Now we can get down to business. First, we initialize the braid. ;;; For Bootstrapping, we define an early version of MAKE. It will be changed ;;; to the real version later on. (define* (make class . initargs) (cond [(or (eq? class ) (eq? class )) (let* ([new (%allocate-instance class (length the-slots-of-a-class))] [dsupers (getarg initargs :direct-supers '())] [dslots (map list (getarg initargs :direct-slots '()))] [cpl (let loop ([sups dsupers] [so-far (list new)]) (if (null? sups) (reverse! so-far) (loop (append (cdr sups) (%class-direct-supers (car sups))) (if (memq (car sups) so-far) so-far (cons (car sups) so-far)))))] [slots (apply append dslots (map %class-direct-slots (cdr cpl)))] [nfields 0] [name (or (getarg initargs :name) '-anonymous-)] [field-initializers '()] ;; this is a temporary allocator version, kept as the original ;; one in tiny-clos. the permanent version below is modified. [allocator (lambda (init) (let ([f nfields]) (set! nfields (+ nfields 1)) (set! field-initializers (cons init field-initializers)) (list (lambda (o) (%instance-ref o f)) (lambda (o n) (%instance-set! o f n)))))] [getters-n-setters (map (lambda (s) (cons (car s) (allocator unspecified-initializer))) slots)]) (%set-class-direct-supers! new dsupers) (%set-class-direct-slots! new dslots) (%set-class-cpl! new cpl) (%set-class-slots! new slots) (%set-class-nfields! new nfields) (%set-class-field-initializers! new (reverse! field-initializers)) (%set-class-getters-n-setters! new getters-n-setters) (%set-class-name! new name) (%set-class-initializers! new '()) ; no class inits now (%set-class-valid-initargs! new #f) ; no initargs now new)] [(eq? class ) (let ([new (%allocate-entity class (length (%class-slots class)))] [arity (getarg initargs :arity #f)] [name (or (getarg initargs :name) '-anonymous-)]) (%set-generic-methods! new '()) (%set-generic-arity! new arity) (%set-generic-name! new name) (%set-generic-combination! new #f) new)] [(eq? class ) (let ([new (%allocate-entity class (length (%class-slots class)))] [name (or (getarg initargs :name) '-anonymous-)]) (%set-method-specializers! new (getarg initargs :specializers)) (%set-method-procedure! new (getarg initargs :procedure)) (%set-method-qualifier! new (or (getarg initargs :qualifier) :primary)) (%set-method-name! new name) (set-instance-proc! new (method:compute-apply-method #f new)) new)])) ;;; These are the real versions of slot-ref and slot-set!. Because of the way ;;; the new slot access protocol works, with no generic call in line, they can ;;; be defined up front like this. Cool eh? ;;>> (slot-ref obj slot) ;;> Pull out the contents of the slot named `slot' in the given `obj'. ;;> Note that slot names are usually symbols, but can be other values as ;;> well. (define* (slot-ref object slot-name) ((lookup-slot-info (class-of object) slot-name cadr) object)) (defsubst (%slot-ref object slot-name) ((lookup-slot-info (class-of object) slot-name cadr) object)) ;;>> (slot-set! obj slot new) ;;> Change the contents of the `slot' slot of `obj' to the given `new' ;;> value. (define* (slot-set! object slot-name new-value) ((lookup-slot-info (class-of object) slot-name caddr) object new-value)) (defsubst (%slot-set! object slot-name new-value) ((lookup-slot-info (class-of object) slot-name caddr) object new-value)) ;;>> (set-slot-ref! obj slot new) ;;> An alias for `slot-set!', to enable using `setf!' on it. (define* set-slot-ref! slot-set!) ;; This is a utility that is used to make locked slots (define (make-setter-locked! g+s key error) (let ([setter (cadr g+s)]) (set-cadr! g+s (lambda (o n) (cond [(and (pair? n) (eq? key (car n)) (not (eq? key #t))) (setter o (cdr n))] [(eq? ??? ((car g+s) o)) (setter o n)] [else (error)]))))) ;;>> (slot-bound? object slot) ;;> Checks if the given `slot' is bound in `object'. See also `???' ;;> above. (define* (slot-bound? object slot-name) (not (eq? ??? (%slot-ref object slot-name)))) (define (lookup-slot-info class slot-name selector) (selector (or (assq slot-name ;; no need to ground slot-ref any more! -- see below ;; (if (eq? class ) ;; ;;* This grounds out the slot-ref tower ;; getters-n-setters-for-class ;; (%class-getters-n-setters class)) (%class-getters-n-setters class)) (raise* make-exn:application:mismatch "slot-ref: no slot `~e' in ~e" slot-name class slot-name)))) ;;; These are for optimizations - works only for single inheritance! (define (%slot-getter class slot-name) (lookup-slot-info class slot-name cadr)) (define (%slot-setter class slot-name) (lookup-slot-info class slot-name caddr)) ;;>>... Singleton and Struct Specifiers ;;; Singleton class. A hash-table is used so it is still possible to compare ;;; classes with eq?. (define singleton-classes (make-hash-table 'weak)) ;;>> (singleton x) ;;> Returns a singleton specification. Singletons can be used as type ;;> specifications that have only one element in them so you can ;;> specialize methods on unique objects. ;;> ;;> This is actually just a list with the symbol `singleton' in its head ;;> and the value, but this function uses a hash table to always return ;;> the same object for the same value. For example: ;;> => (singleton 1) ;;> (singleton 1) ;;> => (eq? (singleton 1) (singleton 1)) ;;> #t ;;> but if the input objects are not `eq?', the result isn't either: ;;> => (eq? (singleton "1") (singleton "1")) ;;> #f ;;> Only `eq?' is used to compare objects. (define* (singleton x) (or (hash-table-get singleton-classes x false-func) (let ([c (list 'singleton x)]) (hash-table-put! singleton-classes x c) c))) ;;>> (singleton? x) ;;> Determines if something is a singleton specification (which is any ;;> list with a head containing the symbol `singleton'). (define* (singleton? x) (and (pair? x) (eq? (car x) 'singleton))) (defsubst (%singleton? x) (and (pair? x) (eq? (car x) 'singleton))) ;;>> (singleton-value x) ;;> Pulls out the value of a singleton specification. (define* singleton-value cadr) ;;>>... ;;> Also note that MzScheme struct types are converted to appropriate ;;> Swindle classes. This way, it is possible to have Swindle generic ;;> functions that work with struct type specializers. ;;>> (struct-type->class struct-type) ;;> This function is used to convert a struct-type to a corresponding ;;> Swindle subclass of `'. See the MzScheme manual for details ;;> on struct types. (define struct-to-class-table (make-hash-table)) (define* (struct-type->class stype) (hash-table-get struct-to-class-table stype (thunk (let-values ([(name init-field-k auto-field-k accessor mutator immutable-k-list super skipped?) (struct-type-info stype)]) (let* ([super (cond [super (struct-type->class super)] [skipped? ] [else ])] [this (parameterize ([*default-object-class* #f]) (make :name name :direct-supers (list super)))]) (hash-table-put! struct-to-class-table stype this) this))))) ;;>>... ;;> *** Common accessors ;;; Given that the early version of MAKE is allowed to call accessors on class ;;; metaobjects, the definitions for them come here, before the actual class ;;; definitions, which are coming up right afterwards. ;;>> (class-direct-slots class) ;;>> (class-direct-supers class) ;;>> (class-slots class) ;;>> (class-cpl class) ;;>> (class-name class) ;;>> (class-initializers class) ;;> Accessors for class objects (look better than using `slot-ref'). (define* (class-direct-slots c) (%slot-ref c 'direct-slots)) (define* (class-direct-supers c) (%slot-ref c 'direct-supers)) (define* (class-slots c) (%slot-ref c 'slots)) (define (class-nfields c) (%slot-ref c 'nfields)) (define (class-field-initializers c) (%slot-ref c 'field-initializers)) (define (class-getters-n-setters c) (%slot-ref c 'getters-n-setters)) (define* (class-cpl c) (%slot-ref c 'cpl)) (define* (class-name c) (%slot-ref c 'name)) (define* (class-initializers c) (%slot-ref c 'initializers)) (define (class-valid-initargs c) (%slot-ref c 'valid-initargs)) ;;>> (generic-methods generic) ;;>> (generic-arity generic) ;;>> (generic-name generic) ;;>> (generic-combination generic) ;;> Accessors for generic function objects. (define* (generic-methods g) (%slot-ref g 'methods)) (define* (generic-arity g) (%slot-ref g 'arity)) (define* (generic-name g) (%slot-ref g 'name)) (define* (generic-combination g) (%slot-ref g 'combination)) ;;>> (method-specializers method) ;;>> (method-procedure method) ;;>> (method-qualifier method) ;;>> (method-name method) ;;>> (method-arity method) ;;> Accessors for method objects. `method-arity' is not really an ;;> accessor, it is deduced from the arity of the procedure (minus one for ;;> the `call-next-method' argument). (define* (method-specializers m) (%slot-ref m 'specializers)) (define* (method-procedure m) (%slot-ref m 'procedure)) (define* (method-qualifier m) (%slot-ref m 'qualifier)) (define* (method-name m) (%slot-ref m 'name)) (define* (method-arity m) (let ([a (procedure-arity (%method-procedure m))]) (cond [(integer? a) (sub1 a)] [(arity-at-least? a) (make-arity-at-least (sub1 (arity-at-least-value a)))] [else (error 'method-arity "the procedure in ~e has bad arity ~e" m a)]))) ;;; These versions will be optimized later. (define %class-direct-slots class-direct-slots) (define %class-direct-supers class-direct-supers) (define %class-slots class-slots) (define %class-nfields class-nfields) (define %class-field-initializers class-field-initializers) (define %class-getters-n-setters class-getters-n-setters) (define %class-cpl class-cpl) (define %class-name class-name) (define %class-initializers class-initializers) (define %class-valid-initargs class-valid-initargs) (define %generic-methods generic-methods) (define %generic-arity generic-arity) (define %generic-name generic-name) (define %generic-combination generic-combination) (define %method-specializers method-specializers) (define %method-procedure method-procedure) (define %method-qualifier method-qualifier) (define %method-name method-name) (define (%set-class-direct-slots! c x) (%slot-set! c 'direct-slots x)) (define (%set-class-direct-supers! c x) (%slot-set! c 'direct-supers x)) (define (%set-class-slots! c x) (%slot-set! c 'slots x)) (define (%set-class-nfields! c x) (%slot-set! c 'nfields x)) (define (%set-class-field-initializers! c x) (%slot-set! c 'field-initializers x)) (define (%set-class-getters-n-setters! c x) (%slot-set! c 'getters-n-setters x)) (define (%set-class-cpl! c x) (%slot-set! c 'cpl x)) (define (%set-class-name! c x) (%slot-set! c 'name x)) (define (%set-class-initializers! c x) (%slot-set! c 'initializers x)) (define (%set-class-valid-initargs! c x) (%slot-set! c 'valid-initargs x)) (define (%set-generic-methods! g x) (%slot-set! g 'methods x)) (define (%set-generic-arity! g x) (%slot-set! g 'arity x)) (define (%set-generic-name! g x) (%slot-set! g 'name x)) (define (%set-generic-combination! g x) (%slot-set! g 'combination x)) (define (%set-method-specializers! m x) (%slot-set! m 'specializers x)) (define (%set-method-procedure! m x) (%slot-set! m 'procedure x)) (define (%set-method-qualifier! m x) (%slot-set! m 'qualifier x)) (define (%set-method-name! m x) (%slot-set! m 'name x)) ;;; These are used to access the two slots that optimize generic invocations. (define (%generic-app-cache g ) (%slot-ref g 'app-cache)) (define (%generic-singletons-list g ) (%slot-ref g 'singletons-list)) (define (%set-generic-app-cache! g x) (%slot-set! g 'app-cache x)) (define (%set-generic-singletons-list! g x) (%slot-set! g 'singletons-list x)) ;;; The next 7 clusters define the 6 initial classes. It takes 7 to 6 because ;;; the first and fourth both contribute to . (define the-slots-of-a-class '(direct-supers ; (class ...) direct-slots ; ((name . options) ...) cpl ; (class ...) slots ; ((name . options) ...) nfields ; an integer field-initializers ; (proc ...) getters-n-setters ; ((slot-name getter setter) ...) name ; a symbol initializers ; (proc ...) valid-initargs)) ; (initarg ...) or #f (define getters-n-setters-for-class ; see lookup-slot-info (map (lambda (s) (let ([f (position-of s the-slots-of-a-class)]) (list s (lambda (o) (%instance-ref o f)) (lambda (o n) (%instance-set! o f n))))) the-slots-of-a-class)) ;;>>... ;;> *** Basic classes ;;>> ;;> This is the "mother of all classes": every Swindle class is an ;;> instance of `'. ;;> Slots: ;;> * direct-supers: direct superclasses ;;> * direct-slots: direct slots, each a list of a name and options ;;> * cpl: class precedence list (classes list this to ) ;;> * slots: all slots (like direct slots) ;;> * nfields: number of fields ;;> * field-initializers: a list of functions to initialize slots ;;> * getters-n-setters: an alist of slot-names, getters, and setters ;;> * name: class name (usually the defined identifier) ;;> * initializers: procedure list that perform additional initializing ;;> See the `clos' documentation for available class and slot keyword ;;> arguments and their effect. (define* (%allocate-instance #f (length the-slots-of-a-class))) (set-instance-class-to-self! ) ;; In the original tiny-clos, this block used to just set the getters-n-setters ;; slot of a class to '() since it wasn't used anyway. In Swindle the MOP ;; accessors are all optimized to directly get the vector element because the ;; meta hierarchy is assumed to be single-inheritance only (allocation of more ;; slots always come after the built in ones), so what I do here is set the ;; slot value properly, and since `%class-getters-n-setters' accesses the ;; vector directly it doesn't go through slot-ref, which means that the ;; slot-ref definition above is fine. So, ;; (%set-class-getters-n-setters! getters-n-setters-for-class) ;; translates into this: ((caddr (assq 'getters-n-setters getters-n-setters-for-class)) getters-n-setters-for-class) ;; and now the direct `%class-getters-n-setters' version: (set! %class-getters-n-setters ;; and (lookup-slot-info 'getters-n-setters caddr) translates to: (cadr (assq 'getters-n-setters getters-n-setters-for-class))) ;;>> ;;> This is the "mother of all values": every value is an instance of ;;> `' (including standard Scheme values). (define* (make :direct-supers '() :direct-slots '() :name ')) ;;>> ;;> This is the "mother of all objects": every Swindle object is an ;;> instance of `'. (define* (make :direct-supers (list ) :direct-slots '() :name ')) ;;; This cluster, together with the first cluster above that defines ;;; and sets its class, have the effect of: ;;; (define ;;; (make :direct-supers (list ) ;;; :direct-slots '(direct-supers ...) ;;; :name ')) (%set-class-direct-supers! (list )) (%set-class-cpl! (list )) (%set-class-direct-slots! (map list the-slots-of-a-class)) (%set-class-slots! (map list the-slots-of-a-class)) (%set-class-nfields! (length the-slots-of-a-class)) (%set-class-field-initializers! (map (lambda (s) unspecified-initializer) the-slots-of-a-class)) (%set-class-name! ') (%set-class-initializers! '()) (%set-class-valid-initargs! #f) ;;>> ;;> The class of all procedures classes, both standard Scheme procedures ;;> classes and entity (Swindle procedure objects) classes. (Note that ;;> this is a class of *classes*). (define* (make :direct-supers (list ) :direct-slots '() :name ')) ;;>> ;;> The class of entity classes -- generic functions and methods. An ;;> entity is a procedural Swindle object, something that you can apply as ;;> a function but it is still a Swindle object. Note that this is the ;;> class of entity *classes* not of entities themselves. (define* (make :direct-supers (list ) :direct-slots '() :name ')) ;;>> ;;> The class of all applicable values: methods, generic functions, and ;;> standard closures. (define* (make :direct-supers (list ) :direct-slots '() :name ')) ;;; The two extra slots below (app-cache and singletons-list) are used to ;;; optimize generic invocations: app-cache holds an 'equal hash-table that ;;; maps a list of classes to the lambda expression that holds the method call ;;; (it used to be an l-hash-table, but 'equal is ok since we can't compare ;;; swindleobj instances recursively -- which is also why tool.ss needs to ;;; redefine the `render-value/format' method). The contents of this slot is ;;; reset whenever a method is added to the generic. Two problems make things ;;; a little more complicated. First, if add-method is used to modify any of ;;; the generic-invocation-generics then all of these caches should be flushed, ;;; this is achieved by setting *generic-app-cache-tag* to a new [list] object ;;; and the value of app-cache is a cons of that value and the actual hash ;;; table - if we see that the car is not eq? to the current tag, then we flush ;;; the cache. Second, singleton values might screw things up, so we hold in ;;; singletons-list a list that has the same length as all method specializer ;;; lists, each element contains a hash table with all singleton values that ;;; appear in that place matched to #t, then when we try to see if we have a ;;; cached function for a generic application, we scan the argument list ;;; against this list, and any value that has a singleton with that value at ;;; some method, is left in place for the app-cache lookup (it is used itself ;;; rather than its class). This whole thing is a bit complicated but leads to ;;; dramatic run-time improvement. ;;>> ;;> The class of generic functions: objects that contain method objects ;;> and calls the appropriate ones when applied. ;;> Slots: ;;> * methods: a list of objects ;;> * arity: the generic arity (same for all of its methods) ;;> * name: generic name ;;> * combination: a method combination function or #f, see ;;> `make-generic-combination' below for details (define* (make :direct-supers (list ) :direct-slots '(methods arity name combination app-cache singletons-list) ; see above :name ')) ;;>> ;;> The class of methods: objects that are similar to Scheme closures, ;;> except that they have type specifiers attached. Note that in contrast ;;> to Tiny CLOS, methods are applicable objects in Swindle -- they check ;;> supplied argument types when applied. ;;> Slots: ;;> * specializers: a list of class (and singleton) specializers ;;> * procedure: the function (never call directly!) ;;> * qualifier: some qualifier tag, used when applying a generic ;;> * name: method name (define* (make :direct-supers (list ) :direct-slots '(specializers procedure qualifier name) :name ')) ;; Do this since compute-apply-method relies on them not changing, as well as a ;; zillion other places. A method should be very similar to a lambda. (dolist [slot '(specializers procedure qualifier)] (make-setter-locked! (lookup-slot-info slot cdr) #t (lambda () (raise* make-exn:application:mismatch "slot-set!: slot `~e' in is locked" slot slot)))) ;;>>... ;;> *** Convenience functions ;;> ;;> These are some convenience functions -- no new syntax, just function ;;> wrappers for `make' with some class and some slot values. See `clos' ;;> for a more sophisticated (and convenient) approach. ;;; These are the convenient syntax we expose to the base-level user. ;;>> (make-class direct-supers direct slots) ;;> Creates a class object -- an instance of . (define* (make-class direct-supers direct-slots) (make :direct-supers direct-supers :direct-slots direct-slots)) ;;>> (make-generic-function [name/arity]) ;;> Creates a generic function object -- an instance of . The ;;> argument can specify name and/or arguments number. (define* (make-generic-function . name/arity) (cond [(null? name/arity) (make )] [(null? (cdr name/arity)) (let ([n/a (car name/arity)]) (if (integer? n/a) (make :arity n/a) (make :name n/a)))] [else (make :name (car name/arity) :arity (cadr name/arity))])) ;;>> (make-method specializers procedure) ;;> Creates a method object -- an instance of , using the given ;;> specializer list and procedure. The procedure should have a first ;;> argument which is being used to access a `call-next-method' call. (define* (make-method specializers procedure) (make :specializers specializers :procedure procedure)) ;;>> (no-next-method generic method [args ...]) ;;>> (no-applicable-method generic [args ...]) ;;> These two generic functions are equivalents to the ones in CL. The ;;> first one is applied on a generic and a method in case there was no ;;> next method and `call-next-method' was used. The second is used when ;;> a generic was called but no matching primary methods were found. The ;;> only difference is that in Swindle methods can be applied directly, ;;> and if `call-next-method' is used, then `no-next-method' gets `#f' for ;;> the generic argument. (define* no-next-method (make-generic-function 'no-next-method)) (define* no-applicable-method (make-generic-function 'no-applicable-method)) ;;; Add possibility of generic-independent method application - this is the ;;; instance-proc of methods, which is activated when you apply the object (in ;;; the original, methods could not be applied). This is defined using this ;;; name and arguments because it is later used directly by the generic ;;; function (cannot use the generic in the inital make since methods need to ;;; be created when the generics are constructed). (define (method:compute-apply-method call-next-method method) (let* ([specializers (%method-specializers method)] [*no-next-method* ; see the *no-next-method* trick below (lambda args (no-next-method #f method . args))] [proc (%method-procedure method)] [arity (method-arity method)] [exact? (integer? arity)] [required ((if exact? identity arity-at-least-value) arity)]) (when (and exact? (> (length specializers) required)) (error 'compute-apply-method "got ~e specializers for ~s - too much for procedure arity ~a" (length specializers) (%method-name method) required)) (lambda args (cond [(if exact? (not (= (length args) required)) (< (length args) required)) (raise* make-exn:application:arity "method ~a: expects ~a~e argument~a, given ~e~a" (%method-name method) (if exact? "" "at least ") required (if (= 1 required) "" "s") (length args) (if (null? args) "" (format ": ~e" args)) (length args) arity)] [(not (every instance-of? args specializers)) (let loop ([args args] [specs specializers]) (if (instance-of? (car args) (car specs)) (loop (cdr args) (cdr specs)) (raise* make-exn:application:type "method ~a: expects argument of type ~a; given ~e" (%method-name method) (%class-name (car specs)) (car args) (car args) (car specs))))] [else (proc *no-next-method* . args)])))) ;;>>... Generics in the instance initialization protocol ;;> The following generic functions are used as part of the protocol of ;;> instantiating an instance, and some are used specifically to instantiate ;;> class objects. ;;; The instance structure protocol. ;;>> (allocate-instance class) ;;> This generic function is called to allocate an instance of a class. ;;> It is applied on the class object, and is expected to return the new ;;> instance object of that class. (define* allocate-instance (make-generic-function 'allocate-instance)) ;;>> (initialize instance initargs) ;;> This generic is called to initialize an instance. It is applied on ;;> the newly allocated object and the given initargs, and is not expected ;;> to return any meaningful value -- only do some side effects on the ;;> instance to initialize it. When overriding this for a some class, it ;;> is not a good idea to skip `call-next-method' since it is responsible ;;> for initializing slot values. (define* initialize (make-generic-function 'initialize)) ;;>> (compute-getter-and-setter class slot allocator) ;;> This generic is used to get a getter and setter functions for a given ;;> slot. It is passed the class object, the slot information (a list of ;;> a slot name and options), and an allocator function. The allocator is ;;> a function that gets an initializer function and returns an index ;;> position for the new slot. The return value should be a list of two ;;> elements -- a getter and a setter functions. (define* compute-getter-and-setter (make-generic-function 'compute-getter-and-setter)) ;;; The class initialization protocol. ;;>> (compute-cpl class) ;;> This generic is used to get the class-precedence-list for a class ;;> object. The standard object uses the `compute-std-cpl' (see ;;> in the code) which flattens the class ancestors using a topological ;;> sort that resolve ambiguities left-to-right. (define* compute-cpl (make-generic-function 'compute-cpl)) ;;>> (compute-slots class) ;;> This generic is used to compute all slot information for a given ;;> class, after its precedence list has been computed. The standard ;;> collects information from all preceding classes. (define* compute-slots (make-generic-function 'compute-slots)) ;;>> (compute-apply-method method) ;;> This generic is used to compute the procedure that will get executed ;;> when a method is applied directly. (define* compute-apply-method (make-generic-function 'compute-apply-method)) ;;>>... Generics in the generic invocation protocol ;;> These generics are used for invocation of generic functions. See the ;;> code to see how this circularity is achieved. ;;>> ((compute-apply-generic generic) args ...) ;;> This generic is used to compute the object (a closure) that is ;;> actually applied to execute the generic call. The standard version ;;> uses `compute-method' and `compute-apply-methods' below, and caches ;;> the result. (define* compute-apply-generic (make-generic-function 'compute-apply-generic)) ;;>> (compute-methods generic args) ;;> Computes the methods that should be applied for this generic ;;> invocation with args. The standard code filters applicable methods ;;> and sorts them according to their specificness. The return value is ;;> expected to depend only on the types of the arguments (and values if ;;> there are singleton specializers). (define* compute-methods (make-generic-function 'compute-methods)) ;;>> ((compute-method-more-specific? generic) mthd1 mthd2 args) ;;> Get a generic and return a function that gets two methods and a list ;;> of arguments and decide which of the two methods is more specific. ;;> This decision should only be based on the argument types, or values ;;> only in case of singletons. (define* compute-method-more-specific? (make-generic-function 'compute-method-more-specific?)) ;;>> ((compute-apply-methods generic methods) args ...) ;;> Gets a generic and returns a function that gets the given arguments ;;> for this call. This function which it returns is the combination of ;;> all given methods. The standard one arranges them by default using ;;> the `call-next-method' argument that methods have. Swindle extends ;;> this with qualified methods and applies `before', `after', and ;;> `around' methods in a similar way to CLOS: first the `around' methods ;;> are applied (and they usually call their `call-next-method' to ;;> continue but can return a different value), then all the `before' ;;> methods are applied (with no `call-next-method'), then all `primary' ;;> methods as usual (remembering the return value), and finally the ;;> `after' methods (similar to the `before', but in reverse specificness ;;> order). If the generic has a `combination' slot value, then it is a ;;> procedure that is used to combine the primary methods, but the ;;> auxiliary ones are still applied in the same way. This is unlike CLOS ;;> where the standard combinations run only `around' methods, and there ;;> is generally more control with method combinations, but in Swindle ;;> `compute-apply-methods' should be overridden for this. See ;;> `make-generic-combination' for details about method combinations. (define* compute-apply-methods (make-generic-function 'compute-apply-methods)) ;;; The next thing to do is bootstrap generic functions. (define generic-invocation-generics (list compute-apply-generic compute-methods compute-method-more-specific? compute-apply-methods)) ;;; This is used to signal whenever all method caches are to be reset - so when ;;; a method is added to generic-invocation-generics, this is set to some value ;;; which is not eq? to the current one. (define *generic-app-cache-tag* #t) ;;>> (add-method generic method) ;;> This generic function is called to add a method to a generic function ;;> object. This is an other change from the original Tiny CLOS where it ;;> was a normal function. (define* (add-method generic method) ;; add singleton specializer value (if any) to the corresponding hash table ;; in singletons-list. (define (add-to-singletons-list specs tables) (unless (null? specs) (when (%singleton? (car specs)) (unless (car tables) (set-car! tables (make-hash-table 'weak))) (hash-table-put! (car tables) (singleton-value (car specs)) #t)) (add-to-singletons-list (cdr specs) (cdr tables)))) (define (n-falses n) (let loop ([n n] [r '()]) (if (zero? n) r (loop (sub1 n) (cons #f r))))) (let ([tables (%generic-singletons-list generic)] [specs (%method-specializers method)] [qualifier (%method-qualifier method)]) ;; make sure that tables always contain enough hash tables (or #f's) (cond [(eq? tables ???) (set! tables (n-falses (length specs))) (%set-generic-singletons-list! generic tables)] [(< (length tables) (length specs)) (set! tables (append! tables (n-falses (- (length specs) (length tables))))) (%set-generic-singletons-list! generic tables)]) (add-to-singletons-list specs tables) (if (memq generic generic-invocation-generics) ;; reset all caches by changing the value of *generic-app-cache-tag* (set! *generic-app-cache-tag* (list #f)) ;; reset this generic app-cache (%set-generic-app-cache! generic ???)) (%set-generic-methods! generic (cons method (filter (lambda (m) (not (and (every eq? (method-specializers m) specs) (eq? (%method-qualifier m) qualifier)))) (%generic-methods generic)))) (set-instance-proc! generic (compute-apply-generic generic)))) ;;; Adding a method calls COMPUTE-APPLY-GENERIC, the result of which calls the ;;; other generics in the generic invocation protocol. Two, related, problems ;;; come up. A chicken and egg problem and a infinite regress problem. ;;; In order to add our first method to COMPUTE-APPLY-GENERIC, we need ;;; something sitting there, so it can be called. The first definition below ;;; does that. ;;; Then, the second definition solves both the infinite regress and the not ;;; having enough of the protocol around to build itself problem the same way: ;;; it special cases invocation of generics in the invocation protocol. (set-instance-proc! compute-apply-generic (lambda (generic) ((%method-procedure (car (%generic-methods generic))) '() generic))) (add-method compute-apply-generic (make-method (list ) (named-lambda method:compute-apply-generic (call-next-method generic) #| The code below is the original, then comes the optimized version below ;; see the definition of the class above. (lambda args (if (and (memq generic generic-invocation-generics) ;* Ground case (memq (car args) generic-invocation-generics)) (apply (%method-procedure (last (%generic-methods generic))) #f args) ((compute-apply-methods generic) (compute-methods generic args) . args))) |# ;; This function converts the list of arguments to a list of keys to look ;; for in the cache - use the argument's class except when there is a ;; corresponding singleton with the same value at the same position. (define (get-keys args tables) (let loop ([args args] [tables tables] [ks '()]) (if (or (null? tables) (null? args)) (reverse! ks) (loop (cdr args) (cdr tables) (cons (if (and (car tables) (hash-table-get (car tables) (car args) false-func)) (car args) (class-of (car args))) ks))))) ;; This is the main function that brings the correct value from the ;; cache, or generates one and store it if there is no entry, or the ;; cache was reset. Finally, it is applied to the arguments as usual. ;; NOTE: This code is delicate! Handle with extreme care! (lambda args (let ([app-cache (%generic-app-cache generic)] [arity (%generic-arity generic)] [keys (get-keys args (%generic-singletons-list generic))] [ground? (and ;* Ground case (memq generic generic-invocation-generics) (pair? args) (memq (car args) generic-invocation-generics))]) ;; This function creates the cached closure -- the assumption is that ;; `keys' contain a specification that will identify all calls that ;; will have this exact same list. (define (compute-callable) (let ([c (if ground? (let ([m (%method-procedure (last (%generic-methods generic)))]) (lambda args (apply m #f args))) (compute-apply-methods generic (compute-methods generic args)))]) (hash-table-put! (cdr app-cache) keys c) c)) (when (cond [(not arity) #f] [(integer? arity) (not (= (length args) arity))] [else (< (length args) (arity-at-least-value arity))]) (let ([least (and (arity-at-least? arity) (arity-at-least-value arity))]) (raise* make-exn:application:arity "generic ~a: expects ~a~e argument~a, given ~e~a" (%generic-name generic) (if least "at least " "") (or least arity) (if (= 1 (or least arity)) "" "s") (length args) (if (null? args) "" (format ": ~e" args)) (length args) arity))) (when (or (eq? app-cache ???) (not (eq? (car app-cache) *generic-app-cache-tag*))) (set! app-cache (cons *generic-app-cache-tag* (make-hash-table 'weak 'equal))) (%set-generic-app-cache! generic app-cache)) ((hash-table-get (cdr app-cache) keys compute-callable) . args)))))) (add-method compute-methods (make-method (list ) (named-lambda method:compute-methods (call-next-method generic args) (let ([more-specific? (compute-method-more-specific? generic)]) (sort (filter (lambda (m) ;; Note that every only goes as far as the shortest list (every instance-of? args (%method-specializers m))) (%generic-methods generic)) (lambda (m1 m2) (more-specific? m1 m2 args))))))) (add-method compute-method-more-specific? (make-method (list ) (named-lambda method:compute-method-more-specific? (call-next-method generic) (lambda (m1 m2 args) (let loop ([specls1 (%method-specializers m1)] [specls2 (%method-specializers m2)] [args args]) (cond [(and (null? specls1) (null? specls2)) (if (eq? (%method-qualifier m1) (%method-qualifier m2)) (error 'generic "two methods are equally specific in ~e" generic) #f)] ;; some methods in this file have less specializers than ;; others, for things like args -- so remove this, leave the ;; args check but treat the missing as if it's ;; ((or (null? specls1) (null? specls2)) ;; (error 'generic ;; "two methods have different number of ~ ;; specializers in ~e" generic)) [(null? args) ; shouldn't happen (error 'generic "fewer arguments than specializers for ~e" generic)] [(null? specls1) ; see above -> treat this like (if (eq? (car specls2)) (loop specls1 (cdr specls2) (cdr args)) #f)] [(null? specls2) ; see above -> treat this like (if (eq? (car specls1)) (loop (cdr specls1) specls2 (cdr args)) #t)] [else (let ([c1 (car specls1)] [c2 (car specls2)]) (if (eq? c1 c2) (loop (cdr specls1) (cdr specls2) (cdr args)) (more-specific? c1 c2 (car args))))])))))) (add-method compute-apply-methods (make-method (list ) (named-lambda method:compute-apply-methods (call-next-method generic methods) (let ([primaries '()] [arounds '()] [befores '()] [afters '()] [combination (%generic-combination generic)]) ;; *** Trick: this (and in above) is the only code that is ;; supposed to ever apply a method procedure. So, the closure that ;; will invoke `no-next-method' is named `*no-next-method*' so it is ;; identifiable. The only way to break this would be to call the ;; method-procedure directly on an object with such a name. (define one-step (if combination (combination generic) (lambda (tail args) (lambda newargs ;; tail is never null: (null? (cdr tail)) below, and the fact ;; that this function is applied on the primaries which are ;; never null (let ([args (if (null? newargs) args newargs)]) ((cdar tail) (if (null? (cdr tail)) (named-lambda *no-next-method* args (no-next-method generic (caar tail) . args)) (one-step (cdr tail) args)) . args)))))) (define ((apply-before/after-method args) method) ((cdr method) (named-lambda *no-next-method* args (no-next-method generic (car method) . args)) . args)) (define ((call-before-primary-after args) . newargs) ;; could supply newargs below, but change before calling befores (let ([args (if (null? newargs) args newargs)]) (for-each (apply-before/after-method args) befores) (begin0 ((one-step primaries args)) (for-each (apply-before/after-method args) afters)))) (define (one-around-step tail args) (if (null? tail) (call-before-primary-after args) (lambda newargs (let ([args (if (null? newargs) args newargs)]) ((cdar tail) (one-around-step (cdr tail) args) . args))))) ;; first sort by qualifier and pull out method-procedures (let loop ([ms methods]) (unless (null? ms) (letsubst ([(push! p) (set! p (cons (cons (car ms) (%method-procedure (car ms))) p))]) (case (%method-qualifier (car ms)) [(:primary) (push! primaries)] [(:around) (push! arounds)] [(:before) (push! befores)] [(:after) (push! afters)] ;; ignore other qualifiers ;; [else (error 'compute-apply-methods ;; "a method ~e has an unexpected qualifier `~e'" ;; (car methods) ;; (%method-qualifier (car methods)))] ) (loop (cdr ms))))) (set! primaries (reverse! primaries)) (set! arounds (reverse! arounds)) (set! befores (reverse! befores)) ;; no reverse! for afters (cond [(null? primaries) (lambda args (no-applicable-method generic . args))] ;; optimize common case of only primaries [(and (null? befores) (null? afters) (null? arounds)) ;; args is initialized to () since if it is a generic of no ;; arguments then it will always stay so, otherwise, the first ;; call will have the real arguments anyway (one-step primaries '())] [else (one-around-step arounds '())]))))) ;;>> (((make-generic-combination keys...) generic) tail args) ;;> This function can be used to construct simple method combinations that ;;> can be used with the `combination' slot of generic functions. The ;;> combination itself is a function that gets a generic and returns a ;;> function that gets a list of method/procedure pairs (for optimization ;;> the method-procedures are pre taken) and the arguments and performs ;;> the call -- but this is only interesting if there's any need to ;;> implement a method combination directly, otherwise, the ;;> `make-generic-combination' interface should allow enough freedom. ;;> Note that when a method combination is used, `around', `before', and ;;> `after' are called around the primary call as usual, but the primaries ;;> are never called with a valid `call-next-method' argument. ;;> ;;> The keyword arguments that can be taken determine the behavior of this ;;> combination. Overall, it is roughly like a customizable version of a ;;> fold operation on the method calls. ;;> * :init ;;> - The initial value for this computation. Defaults to null. ;;> * :combine ;;> - A function to be called on a method call result and the old value, ;;> and produces a new value. The default is `cons', which with an ;;> initial null value will collect the results into a reversed list. ;;> * :process-methods ;;> - A function that can be called on the initial list of ;;> method/procedure pairs to change it -- for example, it can be ;;> reversed to apply the methods from the least specific to the most ;;> specific. No default. ;;> * :process-result ;;> - A function that can be called on the final resulting value to ;;> produce the actual return value. For example, it can reverse back ;;> a list of accumulated values. No default. ;;> * :control ;;> - If this parameter is specified, then the `:combine' argument is ;;> ignored. The value given to `:control' should be a function of ;;> four arguments: ;;> 1. a `loop' function that should be called on some new value and ;;> some new tail; ;;> 2. a `val' argument that gets the current accumulated value; ;;> 3. a `this' thunk that can be called to apply the current method ;;> and return its result; ;;> 4. a `tail' value that holds the rest of the method/procedure list ;;> which can be sent to `loop'. ;;> It should be clear now, that a `:control' argument can have a lot ;;> of control on the computation, it can abort, change arbitrary ;;> values and skip calling methods. Note that if it won't call ;;> `loop' with an empty list, then a `:process-result' function will ;;> not be used as well. See the pre-defined combinations in the ;;> source code to see examples of using this function. (define* (make-generic-combination &key [init '()] [combine cons] process-methods process-result control) (lambda (generic) (lambda (tail dummy-args) (let ([tail (if process-methods (process-methods tail) tail)]) (lambda args (let loop ([res init] [tail tail]) ;; see *no-next-method* trick above (let ([*no-next-method* (lambda args (no-next-method generic (caar tail) . args))]) (if (null? tail) (if process-result (process-result res) res) (if control (control loop res (lambda () ((cdar tail) *no-next-method* . args)) (cdr tail)) (loop (combine ((cdar tail) *no-next-method* . args) res) (cdr tail))))))))))) ;;>> generic-+-combination ;;>> generic-list-combination ;;>> generic-min-combination ;;>> generic-max-combination ;;>> generic-append-combination ;;>> generic-append!-combination ;;>> generic-begin-combination ;;>> generic-and-combination ;;>> generic-or-combination ;;> These are all functions that can be used as a `combination' value for ;;> a generic function. They work in the same way as the standard method ;;> combinations of CL. Most of them do the obvious thing based on some ;;> function to combine the result. The `begin' combination simply ;;> executes all methods one by one and returns the last value, the `and' ;;> and `or' combinations will call them one by one until a false or true ;;> result is returned. The source of these can be used as templates for ;;> defining more combinations. (define* generic-+-combination (make-generic-combination :init 0 :combine +)) (define* generic-list-combination (make-generic-combination :process-result reverse!)) (define* generic-min-combination (make-generic-combination :process-result (lambda (r) (apply min r)))) (define* generic-max-combination (make-generic-combination :process-result (lambda (r) (apply max r)))) (define* generic-append-combination (make-generic-combination :process-result (lambda (r) (apply append (reverse! r))))) (define* generic-append!-combination (make-generic-combination :process-result (lambda (r) (apply append! (reverse! r))))) (define* generic-begin-combination (make-generic-combination :init #f :combine (lambda (x y) x))) (define* generic-and-combination (make-generic-combination :init #t :combine (lambda (x y) (and x y)) :control (lambda (loop val this tail) (and val (loop (this) tail))))) (define* generic-or-combination (make-generic-combination :init #f :combine (lambda (x y) (and x y)) :control (lambda (loop val this tail) (or (this) (loop #f tail))))) ;;>>... ;;> *** More class functionality ;;> (In the following, a `class' can be a class, a singleton specifier, or a ;;> struct type.) ;; optimized helper (defsubst (%struct->class c) (if (struct-type? c) (struct-type->class c) c)) ;;>> (subclass? class1 class2) ;;> Is `class1' a subclass of `class2'? (define* (subclass? c1 c2) (if (%singleton? c1) (if (%singleton? c2) (eq? (singleton-value c1) (singleton-value c2)) (instance-of? (singleton-value c1) (%struct->class c2))) (memq (%struct->class c2) (%class-cpl (%struct->class c1))))) ;;>> (instance-of? x class) ;;> Checks if `x' is an instance of `class' (or one of its subclasses). (define* (instance-of? x c) ;; efficiency: many cases use (all untyped arguments) (or (eq? c ) (if (%singleton? c) ;; efficiency: similar to `subclass?' above (eq? (singleton-value c) x) (memq (%struct->class c) (%class-cpl (%struct->class (class-of x))))))) ;;>> (class? x) ;;> Determines whether `x' is a class. (define* (class? x) (instance-of? x )) (defsubst (%class? x) (instance-of? x )) ;;>> (specializer? x) ;;> Determines whether `x' is a class, a singleton, or a struct-type. (define* (specializer? x) (or (class? x) (%singleton? x) (struct-type? x))) ;;>> (more-specific? class1 class2 x) ;;> Is `class1' more specific than `class2' for the given value? (define* (more-specific? c1 c2 arg) (if (%singleton? c1) (and (eq? (singleton-value c1) arg) (not (and (%singleton? c2) (eq? (singleton-value c1) arg)))) (let ([cc1 (memq (%struct->class c1) (%class-cpl (class-of arg)))]) (and cc1 (memq (%struct->class c2) (cdr cc1)))))) (add-method initialize (make-method (list ) (named-lambda method:initialize (call-next-method object initargs) (error 'initialize "can't initialize an instance of ~e" (class-of object))))) (add-method initialize (make-method (list ) (named-lambda method:initialize (call-next-method object initargs) (let* ([class (class-of object)] [field-initializers (%class-field-initializers class)]) (for-each (lambda (init) (init . initargs)) (%class-initializers class)) (let loop ([n 0] [inits field-initializers]) (when (pair? inits) (%instance-set! object n ((car inits) . initargs)) (loop (+ n 1) (cdr inits)))))))) (add-method initialize (make-method (list ) (named-lambda method:initialize (call-next-method class initargs) (call-next-method) (%set-class-direct-supers! class (let ([default (*default-object-class*)] [supers (getarg initargs :direct-supers)]) ;; check valid supers, and always have an object class (cond [(not default) supers] ; check disabled [(or (not supers) (null? supers)) (list (*default-object-class*))] [(not (list? supers)) (error 'class "bad superclasses: ~e" supers)] [else (let ([c (find-if (lambda (c) (not (and (%class? c) (subclass? c default)))) supers)]) (if c (error 'class "cannot inherit from a ~a, ~e" (if (%class? c) "non-object class" "non-class") c) supers))]))) (%set-class-direct-slots! class (let ([autoinitargs (getarg initargs :autoinitargs)]) (map (lambda (s) (if (pair? s) (if (or (not autoinitargs) (getarg (cdr s) :initarg) (not (symbol? (car s)))) s (list* (car s) :initarg (string->symbol (string-append ":" (symbol->string (car s)))) (cdr s))) (list s))) (getarg initargs :direct-slots '())))) (%set-class-cpl! class (compute-cpl class)) (%set-class-slots! class (compute-slots class)) (%set-class-name! class (or (getarg initargs :name) '-anonymous-)) (let* ([nfields 0] [field-initializers '()] ;; allocator: give me an initializer function, get a slot number [allocator (lambda (init) (let ([f nfields]) (set! nfields (+ nfields 1)) (set! field-initializers (cons init field-initializers)) f))] [getters-n-setters (map (lambda (slot) (cons (car slot) (compute-getter-and-setter class slot allocator))) (%class-slots class))]) (%set-class-nfields! class nfields) (%set-class-field-initializers! class (reverse! field-initializers)) (%set-class-getters-n-setters! class getters-n-setters)) (%set-class-initializers! class (reverse (mappend (lambda (c) (if (instance-of? c ) (%class-initializers c) '())) (cdr (%class-cpl class))))) (%set-class-valid-initargs! ; for sanity checks class (getarg initargs :valid-initargs (thunk (mappend (lambda (slot) (getargs (cdr slot) :initarg)) (%class-slots class)))))))) (add-method initialize (make-method (list ) (named-lambda method:initialize (call-next-method generic initargs) (call-next-method) (%set-generic-methods! generic '()) (%set-generic-arity! generic (getarg initargs :arity #f)) (%set-generic-name! generic (or (getarg initargs :name) '-anonymous-)) (%set-generic-combination! generic (getarg initargs :combination)) (set-instance-proc! generic (lambda args (raise* make-exn:application:mismatch "~s: no methods added yet" (%generic-name generic) generic)))))) (add-method initialize (make-method (list ) (named-lambda method:initialize (call-next-method method initargs) (call-next-method) (%set-method-specializers! method (map (lambda (c) (%struct->class c)) (getarg initargs :specializers))) (%set-method-procedure! method (getarg initargs :procedure)) (%set-method-qualifier! method (or (getarg initargs :qualifier) :primary)) (%set-method-name! method (or (getarg initargs :name) '-anonymous-)) (set-instance-proc! method (compute-apply-method method))))) (add-method allocate-instance (make-method (list ) (named-lambda method:allocate-instance (call-next-method class) (%allocate-instance class (length (%class-field-initializers class)))))) (add-method allocate-instance (make-method (list ) (named-lambda method:allocate-instance (call-next-method class) (%allocate-entity class (length (%class-field-initializers class)))))) (add-method compute-cpl (make-method (list ) (named-lambda method:compute-cpl (call-next-method class) (compute-std-cpl class %class-direct-supers)))) (add-method compute-slots (make-method (list ) (named-lambda method:compute-slots (call-next-method class) (let ([all-slots (map %class-direct-slots (%class-cpl class))] [final-slots #f]) (let collect ([to-process (apply append all-slots)] [result '()]) (if (null? to-process) (set! final-slots result) (let* ([name (caar to-process)] [others '()] [remaining-to-process (filter (lambda (o) (if (eq? (car o) name) (begin (set! others (cons (cdr o) others)) #f) #t)) to-process)]) (collect remaining-to-process (cons (cons name (apply append (reverse! others))) result))))) ;; Sort the slots by order of appearance in cpl, makes them stay in the ;; same index, allowing optimizations for single-inheritance (let collect ([to-process (apply append (reverse! all-slots))] [result '()]) (cond [(null? to-process) (reverse! result)] [(assq (caar to-process) result) (collect (cdr to-process) result)] [else (collect (cdr to-process) (cons (assq (caar to-process) final-slots) result))])))))) (add-method compute-getter-and-setter (make-method (list ) (letrec ([nothing "nothing"] [l-getarg ;; apply getarg on a list of names until get a value (lambda (args initargs) ;; give priority to first initargs (if (null? initargs) nothing (let ([x (getarg args (car initargs) nothing)]) (if (eq? x nothing) (l-getarg args (cdr initargs)) x))))]) (named-lambda method:compute-getter-and-setter (call-next-method class slot allocator) (let ([initargs (getargs (cdr slot) :initarg)] [initializer (getarg (cdr slot) :initializer)] [initvalue (getarg (cdr slot) :initvalue ???)] [type (getarg (cdr slot) :type #f)] [allocation (getarg (cdr slot) :allocation :instance)] [lock (getarg (cdr slot) :lock #f)]) (define init (if initializer (if (eq? 0 (procedure-arity initializer)) (lambda args (initializer)) initializer) (lambda args initvalue))) (define (init-slot . args) (let ([result (l-getarg args initargs)]) (when (eq? result nothing) (set! result (apply init args))) (when (and type (not (eq? result ???)) (not (instance-of? result type))) (error 'class "bad initial value type for slot ~e in ~e (~e not a ~e)" (car slot) class result type)) result)) (when (and type (not (specializer? type))) (error 'class "bad type specifier for ~e: ~e" (car slot) type)) (case allocation [(:instance) (let* ([f (allocator init-slot)] [g+s (list (lambda (o) (%instance-ref o f)) (if (and type (not (eq? type))) (lambda (o n) (if (instance-of? n type) (%instance-set! o f n) (raise* make-exn:application:type "slot-set!: wrong type for slot ~ ~e in ~e (~e not in ~e)" (car slot) class n type (car slot) type))) (lambda (o n) (%instance-set! o f n))))]) (when lock (make-setter-locked! g+s lock (lambda () (raise* make-exn:application:mismatch "slot-set!: slot `~e' in ~e is locked" (car slot) (%class-name class) (car slot))))) g+s)] [(:class) (unless (null? initargs) (let ([setter #f]) (%set-class-initializers! class (cons (lambda args (let ([result (l-getarg args initargs nothing)]) ;; cache the setter (unless setter (set! setter (caddr (assq (car slot) (%class-getters-n-setters class))))) (unless (eq? result nothing) (setter #f result)))) (%class-initializers class))))) (if (and (assq (car slot) (%class-direct-slots class)) (getarg (cdr (assq (car slot) (%class-direct-slots class))) :allocation #f)) ;; the slot was declared as :class here (let* ([cell (init)] ; default value - no arguments [g+s (list (lambda (o) cell) (lambda (o n) (if (and type (not (instance-of? n type))) (raise* make-exn:application:type "slot-set!: wrong type for shared slot ~ ~e in ~e (~e not in ~e)" (car slot) class n type (car slot) type) (set! cell n))))]) (when lock (make-setter-locked! (car slot) g+s lock (lambda () (raise* make-exn:application:mismatch "slot-set!: slot `~e' in ~e is locked" (car slot) (%class-name class) (car slot))))) g+s) ;; the slot was inherited as :class - fetch its getters/setters (let loop ([cpl (cdr (%class-cpl class))]) (cond [(assq (car slot) (%class-getters-n-setters (car cpl))) => cdr] [else (loop (cdr cpl))])))] [else (error 'class "allocation for ~e must be :class or :instance, got ~e" (car slot) allocation)])))))) ;;; Use the previous function when populating this generic. (add-method compute-apply-method (make-method (list ) method:compute-apply-method)) (add-method no-next-method (make-method (list ) (lambda (call-next-method generic method . args) (raise* make-exn:application:mismatch (concat "~s: no applicable next method to call" (case (%method-qualifier method) [(:before) " in a `before' method"] [(:before) " in an `after' method"] [else ""])) (%generic-name generic) generic)))) (add-method no-next-method (make-method (list (singleton #f) ) (lambda (call-next-method generic method . args) (raise* make-exn:application:mismatch "~s: no applicable next method when calling a method directly" (%method-name method) method)))) (add-method no-applicable-method (make-method (list ) (lambda (call-next-method generic . args) (raise* make-exn:application:mismatch "~s: no applicable primary methods for argument types ~e" (%generic-name generic) (map class-of args) generic)))) ;;; --------------------------------------------------------------------------- ;;; Customization variables ;;>>... Swindle Customization Parameters ;;>> *default-method-class* ;;>> *default-generic-class* ;;>> *default-class-class* ;;>> *default-entityclass-class* ;;> These parameters specify default classes for the many constructor ;;> macros in `clos'. (define* *default-method-class* (make-parameter )) (define* *default-generic-class* (make-parameter )) (define* *default-class-class* (make-parameter )) (define* *default-entityclass-class* (make-parameter )) ;; an automatic superclass for all classes -- turned off for the builtins below ;;>> *default-object-class* ;;> This parameter contains a value which is automatically made a ;;> superclass for all classes. Defaults to `'. (define* *default-object-class* (make-parameter #f)) ;;>> *make-safely* ;;> Setting this parameter to #t will make Swindle perform sanity checks ;;> on given initargs for creating an object. This will make things ;;> easier for debugging, but also slower. Defaults to `#f'. Note that ;;> the sanity checks are done in `initialize'. ;; This could be in `make', but `defclass' will call that with no slots to make ;; the object and then call `initialize' with all arguments to actually create ;; the class. (define* *make-safely* (make-parameter #f)) (define (check-initargs class initargs) ;; sanity check - verify sensible keywords given (let ([valid-initargs (%class-valid-initargs class)]) (or (not valid-initargs) (let loop ([args initargs]) (cond [(null? args) #t] [(not (and (pair? args) (pair? (cdr args)))) (error 'make "error in initargs for ~e; arg list not balanced" class)] [(not (symbol? (car args))) (error 'make "error in initargs for ~e; ~e is not a keyword" class (car args))] [(not (memq (car args) valid-initargs)) (error 'make "error in initargs for ~e; unknown keyword: ~e" class (car args))] [else (loop (cddr args))]))))) ;;; --------------------------------------------------------------------------- ;;; Make `make' a generic function ;;>>... Creating Instances ;;; Now everything works, both generic functions and classes, so we can turn on ;;; the real MAKE. ;;; ELI: This is turned into a generic function - do this carefully - first ;;; create the generic function and the method instances, then change make. ;;>> (make class initargs ...) ;;> Create an instance of `class', which can be any Swindle class (except ;;> for some special top-level classes and built-in classes). ;;> ;;> See the `Object Initialization Protocol' below for a description of ;;> generic functions that are involved in creating a Swindle object. (let ([m (make-method (list ) (named-lambda method:make (call-next-method class . initargs) (let ([instance (allocate-instance class)]) (when (*make-safely*) (check-initargs class initargs)) (initialize instance initargs) instance)))] [g (make-generic-function 'make)]) (add-method g m) (set! make g)) ;; The clean concept behind this is due to Joe Marshall. ;;>> (rec-make (name class arg ...) ...) ;;> This is similar to: ;;> ;;> (letrec ([name (make class arg ...)] ...) ;;> (values name ...)) ;;> ;;> except that the names are first bound to allocated instances with no ;;> initargs, and then they are initialized with all these bindings. This ;;> is useful for situations where creating some instances needs other ;;> instances as values. One sample usage is the way `defclass' makes the ;;> class binding available for slot specifications like `:type'. Note ;;> that this is a special form, which invokes `allocate-instance' and ;;> `initialize' directly, so specializing `make' on some input will not ;;> change the way `rec-make' works. (defsubst* (rec-make (name class arg ...) ...) (let ([name (allocate-instance class)] ...) (when (*make-safely*) (check-initargs class (list arg ...)) ...) (initialize name (list arg ...)) ... (values name ...))) ;;; --------------------------------------------------------------------------- ;;; Make `add-method' a generic function ;;; Use this to compute a name for the method. specs is a list of classes or ;;; class-names (in case of unnamed-methods in clos.ss). (define (compute-method-name specs generic-name) (define (spec-string spec) (cond [(%singleton? spec) (format "{~e}" (singleton-value spec))] [(%class? spec) (symbol->string (%class-name (%struct->class spec)))] [else "???"])) (string->symbol (apply string-append (symbol->string generic-name) ":" (if (null? specs) '("()") (cons (spec-string (car specs)) (map (lambda (c) (string-append "," (spec-string c))) (cdr specs))))))) (let ([old-add-method add-method]) (set! add-method (make :name 'add-method :arity 2)) (old-add-method add-method (make-method (list ) (named-lambda method:add-method (call-next-method generic method) (let ([method-arity (method-arity method)] [generic-arity (%generic-arity generic)]) (cond [(not generic-arity) (%set-generic-arity! generic method-arity)] ;; note: equal? works on arity-at-least structs [(not (equal? generic-arity method-arity)) (error 'add-method "wrong arity for `~e', expects ~a; given a method with ~a" (%generic-name generic) (if (integer? generic-arity) generic-arity (format "at-least-~a" (arity-at-least-value generic-arity))) (if (integer? method-arity) method-arity (format "at-least-~a" (arity-at-least-value method-arity))))]) ;; set a name for the method if none (when attached to a generic) (let ([n (%method-name method)]) (unless (and n (not (eq? n '-anonymous-))) (%set-method-name! method (let* ([psym (object-name (%method-procedure method))] [pstr (and psym (symbol->string psym))]) (if (or (not pstr) (regexp-match #rx":[0-9]*:[0-9]*$" pstr)) (compute-method-name (%method-specializers method) (%generic-name generic)) psym))))) (old-add-method generic method)))))) ;;; Optimized frequently used accessors: ;;; This is possible because of the ordering of the slots in compute-slots, ;;; works only for single-inheritance. Note that there is no type checking - ;;; it is unsafe, but makes things around 5-6 times faster! (set! %class-direct-slots (%slot-getter 'direct-slots)) (set! %class-direct-supers (%slot-getter 'direct-supers)) (set! %class-slots (%slot-getter 'slots)) (set! %class-nfields (%slot-getter 'nfields)) (set! %class-field-initializers (%slot-getter 'field-initializers)) (set! %class-getters-n-setters (%slot-getter 'getters-n-setters)) (set! %class-cpl (%slot-getter 'cpl)) (set! %class-name (%slot-getter 'name)) (set! %class-initializers (%slot-getter 'initializers)) (set! %class-valid-initargs (%slot-getter 'valid-initargs)) (set! %generic-methods (%slot-getter 'methods)) (set! %generic-arity (%slot-getter 'arity)) (set! %generic-name (%slot-getter 'name)) (set! %generic-combination (%slot-getter 'combination)) (set! %method-specializers (%slot-getter 'specializers)) (set! %method-procedure (%slot-getter 'procedure)) (set! %method-qualifier (%slot-getter 'qualifier)) (set! %method-name (%slot-getter 'name)) (set! %set-class-direct-slots! (%slot-setter 'direct-slots)) (set! %set-class-direct-supers! (%slot-setter 'direct-supers)) (set! %set-class-slots! (%slot-setter 'slots)) (set! %set-class-nfields! (%slot-setter 'nfields)) (set! %set-class-field-initializers!(%slot-setter 'field-initializers)) (set! %set-class-getters-n-setters! (%slot-setter 'getters-n-setters)) (set! %set-class-cpl! (%slot-setter 'cpl)) (set! %set-class-name! (%slot-setter 'name)) (set! %set-class-initializers! (%slot-setter 'initializers)) (set! %set-class-valid-initargs! (%slot-setter 'valid-initargs)) (set! %set-generic-methods! (%slot-setter 'methods)) (set! %set-generic-arity! (%slot-setter 'arity)) (set! %set-generic-name! (%slot-setter 'name)) (set! %set-generic-combination! (%slot-setter 'combination)) (set! %set-method-specializers! (%slot-setter 'specializers)) (set! %set-method-procedure! (%slot-setter 'procedure)) (set! %set-method-qualifier! (%slot-setter 'qualifier)) (set! %set-method-name! (%slot-setter 'name)) ;; Optimize these internal ones as well. (set! %generic-app-cache (%slot-getter 'app-cache)) (set! %generic-singletons-list (%slot-getter 'singletons-list)) (set! %set-generic-app-cache! (%slot-setter 'app-cache)) (set! %set-generic-singletons-list! (%slot-setter 'singletons-list)) ;;; --------------------------------------------------------------------------- ;;; Built-in classes. ;;>>... Built-in Classes ;;>> ;;> The class of all built-on classes. (define* (make :direct-supers (list ) :direct-slots '() :name ' ;; needed so structs can turn to classes even if *make-safely* :valid-initargs #f)) ;; Normally, can't allocate these. (add-method allocate-instance (make-method (list ) (named-lambda method:allocate-instance (call-next-method class) (error 'allocate-instance "can't instantiate a primitive class ~e" class)))) ;;>> ;;> The superclass of all built-in classes. (define* (make :direct-supers (list ) :direct-slots '() :name ')) (defsubst (defprimclass primclass) (defprimclass primclass ) (_ primclass supers ...) (define* primclass (make :name 'primclass :direct-supers (list supers ...) :direct-slots '()))) ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;>> ;;> These classes represent built-in objects. See the class hierarchy ;;> below for a complete description of the relations between these ;;> classes. ;;>> ;;>> ;;> These are also classes for built-in objects, but they are classes for ;;> MzScheme structs -- which can be used like Swindle classes since they ;;> will get converted to appropriate Swindle subclasses of `'. ;;> `' is a class of structs that are hidden -- see the ;;> documentation for `struct-info' and the `skipped?' result. Note that ;;> structs can be used as long as they can be inspected -- otherwise, we ;;> can't even know that they are structs with `struct?' (this means that ;;> can only appear in the cpl of a struct class that ;;> inherits from a struct which is not under the current inspector). (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) ;; Have all possible number combinations in any case (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) ;; MzScheme stuff (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) (defprimclass ) ;;>> ;;> The class of all Scheme procedures. (define* (make :name ' :direct-supers (list ) :direct-slots '())) ;;>> ;;> The class of all primitive MzScheme procedures. (define* (make :name ' :direct-supers (list ) :direct-slots '())) (*default-object-class* ) ; turn auto-superclass back on (set! class-of (lambda (x) ;; If all Schemes were IEEE compliant, the order of these wouldn't ;; matter? ;; ELI: changed the order so it fits best the expected results. (cond [(instance? x) (instance-class x)] [(procedure? x) (cond [(parameter? x) ] [(primitive? x) ] [else ])] [(string? x) (if (immutable? x) )] [(pair? x) (if (list? x) (if (immutable? x) ) (if (immutable? x) ))] [(null? x) ] [(char? x) ] [(symbol? x) ] [(number? x) (if (exact? x) (cond [(integer? x) ] [(rational? x) ] [(real? x) ] [(complex? x) ] [else ]) ; should not happen (cond [(integer? x) ] [(rational? x) ] [(real? x) ] [(complex? x) ] [else ]))] ; should not happen [(boolean? x) ] [(vector? x) ] [(eof-object? x) ] [(input-port? x) (if (file-stream-port? x) )] [(output-port? x) (if (file-stream-port? x) )] ;; MzScheme stuff [(struct? x) (let-values ([(type _) (struct-info x)]) (if type (struct-type->class type) ))] [(void? x) ] [(box? x) ] [(weak-box? x) ] [(regexp? x) ] [(promise? x) ] [(exn? x) (if (exn:break? x) )] [(semaphore? x) ] [(hash-table? x) ] [(thread? x) ] [(subprocess? x) ] [(syntax? x) (if (identifier? x) )] [(namespace? x) ] [(custodian? x) ] [(tcp-listener? x) ] [(security-guard? x) ] [(will-executor? x) ] [(struct-type? x) ] [(inspector? x) ] [(pseudo-random-generator? x) ] [(compiled-expression? x) ] [else ]))) ;;; --------------------------------------------------------------------------- ;;; Some useful predicates. ;;>> (builtin? x) ;;>> (function? x) ;;>> (generic? x) ;;>> (method? x) ;;> Predicates for instances of , , , and ;;> . (define* (builtin? x) (instance-of? x )) (define* (function? x) (instance-of? x )) (define* (generic? x) (instance-of? x )) (define* (method? x) (instance-of? x )) ;;; --------------------------------------------------------------------------- ;;>>... Class Hierarchy ;;> ;;> In the following, every class's class is specified after a colon. Also, ;;> some classes appear in more than once place due to multiple-inheritance. ;;> ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> : ;;> ... struct type classes ... ;;>>... Object Initialization Protocol ;;> This is the initialization protocol. All of these are generic ;;> functions (unlike the original Tiny CLOS). See the individual ;;> descriptions above for more details. ;;> ;;> make ;;> allocate-instance ;;> initialize ;;> class initialization only: ;;> compute-cpl ;;> compute-slots ;;> compute-getter-and-setter ;;> method initialization only: ;;> compute-apply-method ;;> add-method ;;> compute-apply-generic ;;> compute-methods ;;> compute-method-more-specific? ;;> compute-apply-methods )