class.ss: Classes and Objects

To load: (require (lib "class.ss"))

A class specifies

An object is a collection of bindings for fields that are instantiated according to a class description.

The object system allows a program to define a new class (a derived class) in terms of an existing class (the superclass) using inheritance, overriding, and augmenting:

An interface is a collection of method names to be implemented by a class, combined with a derivation requirement. A class implements an interface when it

A class can implement any number of interfaces. A derived class automatically implements any interface that its superclass implements. Each class also implements an implicitly-defined interface that is associated with the class. The implicitly-defined interface contains all of the class's public method names, and it requires that all other implementations of the interface are derived from the class.

A new interface can extend one or more interfaces with additional method names; each class that implements the extended interface also implements the original interfaces. The derivation requirements of the original interface must be consistent, and the extended interface inherits the most specific derivation requirement from the original interfaces.

Classes, objects, and interfaces are all first-class Scheme values. However, a MzScheme class or interface is not a MzScheme object (i.e., there are no ``meta-classes'' or ``meta-interfaces'').

4.1  Object Example

The following example conveys the object system's basic style.

(define stack<%> (interface () push! pop! none?))

(define stack% 
  (class* object% (stack<%>)
    ; Declare public methods that can be overridden:
    (public push! pop! none?)
    ; Declare a public method that can be augmented, only:
    (pubment print-name)
    
    (define stack null)        ; A private field     
    (init-field (name 'stack)) ; A public field

    ; Method implementations:
    (define (push! v)
      (set! stack (cons v stack)))
    (define (pop!)
      (let ([v (car stack)]) 
         (set! stack (cdr stack)) 
         v))
    (define (none?) 
      (null? stack))
    (define (print-name)
      (display name) 
      (inner (void) print-name) ; Let subclass print more
      (newline))

    ; Call superclass initializer:
    (super-new)))

(define fancy-stack% 
  (class stack%
    ; Declare override
    (override push!)
    ; Implement override:
    (define (push! v)
      (super push! (cons 'fancy v)))
      
    ; Add inherited field to local environment
    (inherit-field name)

    ; Declare augment
    (augment print-name)
    ; Implement augment
    (define (print-name)
      (when (equal? name 'Bob)
        (display ", Esq."))
      (inner (void) print-name))

    (super-new)))

(define double-stack% 
  (class stack%
    (inherit push!)

    (public double-push!)
    (define (double-push! v) (push! v) (push! v))

    ; Always supply name
    (super-new (name 'double-stack))))

(define-values (make-safe-stack-class is-safe-stack?)
  (let ([safe-stack<%> (interface (stack<%>))])
    (values
     (lambda (super%)
       (class* super% (safe-stack<%>)
         (inherit none?)
         (override pop!)
         (define (pop!) 
           (if (none?) 
               #f 
               (super pop!)))
         (super-new)))
     (lambda (obj)
       (is-a? obj safe-stack<%>)))))

(define safe-stack% (make-safe-stack-class stack%))

The interface stack<%>1 defines the ever-popular stack interface with the methods push!, pop!, and none?. Since it has no superinterfaces, the only derivation requirement of stack<%> is that its classes are derived from the built-in empty class, object%. The class stack%2 is derived from object% and implements the stack<%> interface. Three additional classes are derived from the basic stack% implementation:

In each derived class, the (super-new ...) form causes the superclass portion of the object to be initialized, including the initialization of its fields.

The creation of safe-stack% illustrates the use of classes as first-class values. Applying make-safe-stack-class to fancy-stack% or double-stack% -- indeed, any class with push, pop!, and none? methods -- creates a ``safe'' version of the class. A stack object can be recognized as a safe stack by testing it with is-safe-stack?; this predicate returns #t only for instances of a class created with make-safe-stack-class (because only those classes implement the safe-stack<%> interface).

In each of the example classes, the field name contains the name of the class. The name instance variable is introduced as a new instance variable in stack%, and it is declared there with the init-field keyword, which means that an instantiation of the class can specify the initial value, but it defaults to 'stack. The double-stack% class provides name when initializing the stack% part of the object, so a name cannot be supplied when instantiating double-stack%. When the print-name method of an object from double-stack% is invoked, the name printed to the screen is always ``double-stack''.

While all of fancy-stack%, double-stack%, and safe-stack% inherit the push! method of stack%, it is declared with inherit only in double-stack%; new declarations in fancy-stack% and safe-stack% do not need to refer to push!, so the inheritance does not need to be declared. Similarly, only safe-stack% needs to declare (inherit none?).

The fancy-stack% class overrides pop! to extend the implementation of pop!. The new definition of pop! must accesses the original pop! method that is defined in stack% through the super form.

The stack% class declares its print-name method using pubment, which means that the method is public, but it can only be augmented in subclasses, and not overridden. The implementation of print-name uses inner to execute a subclass-supplied augmenting method. If no such augmenting method is available, the (void) expression is evaluated, instead. The fancy-stack% classes uses augment to declare an augmentation of print-name, and also uses inner to allow further augmenting in later subclasses.

The instantiate form, the new form, and the make-object procedure all create an object from a class. The instantiate form supports initialization arguments by both position and name, the new form only supports by name initialization arguments, and make-object supports initialization arguments by position only. The following examples create objects using the classes above:

(define stack (make-object stack%))
(define fred (new stack% (name 'Fred)))
(define joe (instantiate stack% () (name 'Joe)))
(define double-stack (make-object double-stack%))
(define safe-stack (new safe-stack% (name 'safe)))

The send form calls a method on an object, finding the method by name. The following example uses the objects created above:

(send stack push! fred)
(send stack push! double-stack)
(let loop ()
  (if (not (send stack none?))
    (begin
      (send (send stack pop!) print-name)
      (loop))))

This loop displays 'double-stack and 'Fred to the standard output port.

4.2  Creating Interfaces

The interface form creates a new interface:

(interface (super-interface-expr ···) identifier ···)

All of the identifiers must be distinct.

Each super-interface-expr is evaluated (in order) when the interface expression is evaluated. The result of each super-interface-expr must be an interface value, otherwise the exn:fail:object exception is raised. The interfaces returned by the super-interface-exprs are the new interface's superinterfaces, which are all extended by the new interface. Any class that implements the new interface also implements all of the superinterfaces.

The result of an interface expression is an interface that includes all of the specified identifiers, plus all identifiers from the superinterfaces. Duplicate identifier names among the superinterfaces are ignored, but if a superinterface contains one of the identifiers in the interface expression, the exn:fail:object exception is raised.

If no super-interface-exprs are provided, then the derivation requirement of the resulting interface is trivial: any class that implements the interface must be derived from object%. Otherwise, the implementation requirement of the resulting interface is the most specific requirement from its superinterfaces. If the superinterfaces specify inconsistent derivation requirements, the exn:fail:object exception is raised.

4.3  Creating Classes

The built-in class object% has no methods fields, implements only its own interface (class->interface object%), and is transparent (i.e,. its inspector is #f, so all immediate instances are equal?). All other classes are derived from object%.

The class* form creates a new class:

(class* superclass-expr (interface-expr ···)
  class-clause
  ···)

class-clause is one of
  (inspect inspector-expr)
  (init init-declaration ···)
  (init-field init-declaration ···)
  (field field-declaration ···)
  (inherit-field optionally-renamed-id ···)
  (init-rest id)
  (init-rest)
  (public optionally-renamed-id ···)
  (pubment optionally-renamed-id ···)
  (public-final optionally-renamed-id ···)
  (override optionally-renamed-id ···)
  (overment optionally-renamed-id ···)
  (override-final optionally-renamed-id ···)
  (augment optionally-renamed-id ···)
  (augride optionally-renamed-id ···)
  (augment-final optionally-renamed-id ···)
  (private id ···)
  (inherit optionally-renamed-id ···)
  (rename-super renamed-id ···)
  (rename-inner renamed-id ···)
  method-definition
  definition
  expr
  (begin class-clause ···)

init-declaration is one of
  identifier
  (optionally-renamed-id)
  (optionally-renamed-id default-value-expr)

field-declaration is
  (optionally-renamed-id default-value-expr)

optionally-renamed-id is one of
  identifier
  renamed-id

renamed-id is
  (internal-id external-id)

method-definition is
  (define-values (identifier) method-procedure)

method-procedure is
  (lambda formals expr ···1)
  (case-lambda (formals expr ···1) ···)
  (let-values (((identifier) method-procedure) ···) method-procedure)
  (letrec-values (((identifier) method-procedure) ···) method-procedure)
  (let-values (((identifier) method-procedure) ···1) identifier)
  (letrec-values (((identifier) method-procedure) ···1) identifier)

The superclass-expr expression is evaluated when the class* expression is evaluated. The result must be a class value (possibly object%), otherwise the exn:fail:object exception is raised. The result of the superclass-expr expression is the new class's superclass.

The interface-expr expressions are also evaluated when the class* expression is evaluated, after superclass-expr is evaluated. The result of each interface-expr must be an interface value, otherwise the exn:fail:object exception is raised. The interfaces returned by the interface-exprs are all implemented by the class. For each identifier in each interface, the class (or one of its ancestors) must declare a public method with the same name, otherwise the exn:fail:object exception is raised. The class's superclass must satisfy the implementation requirement of each interface, otherwise the exn:fail:object exception is raised.

An inspect class-clause selects an inspector (see section 4.5 in PLT MzScheme: Language Manual) for the class extension. The inspector-expr must evaluate to an inspector or #f when the class* form is evaluated. Just as for structure types, an inspector controls access to the class's fields, including private fields, and also affects comparisons using equal?. If no inspect clause is provided, access to the class is controlled by the parent of the current inspector (see section 4.5 in PLT MzScheme: Language Manual). A syntax error is reported if more than one inspect clause is specified.

The other class-clauses define initialization arguments, public and private fields, and public and private methods. For each identifier or optionally-renamed-id in a public, override, augment, pubment, overment, augride, public-final, override-final, augment-final, or private clause, there must be one method-definition. All other definition class-clauses create private fields. All remaining exprs are initialization expressions to be evaluated when the class is instantiated (see section 4.4).

The result of a class* expression is a new class, derived from the specified superclass and implementing the specified interfaces. Instances of the class are created with the instantiate form or make-object procedure, as described in section 4.4.

Each class-clause is (partially) macro-expanded to reveal its shapes. If a class-clause is a begin expression, its sub-expressions are lifted out of the begin and treated as class-clauses, in the same way that begin is flattened for top-level and embedded definitions.

Within a class* form for instances of the new class, this is bound to the object itself; super-instantiate, super-make-object, and super-new are bound to forms to initialize fields in the superclass (see section 4.4); super is available for calling superclass methods (see section 4.3.3.1); and inner is available for calling subclass augmentations of methods (see section 4.3.3.1).

The public, override, augment, pubment, overment, augride, public-final, override-final, augment-final, private, inherit, rename-super, rename-inner this, super, inner, super-instantiate, super-make-object, and super-new keywords are all exported by class.ss as syntactic forms that raise an error when used outside of a class declaration.

The class form is like class, but omits the interface-exprs, for the case that none are needed:

(class superclass-expr
  class-clause
  ···)

The public*, pubment*, public-final*, override*, overment*, override-final*, augment*, augride*, augment-final*, and private* forms abbreviate a public, etc. declaration and a sequence of definitions:

 (public* (name expr) ···)
=expands=>
 (begin
  (public name ···)
  (define name expr) ···)

etc.

The define/public, define/pubment, define/public-final, define/override, define/overment, define/override-final, define/augment, define/augride, define/augment-final, and define/private forms similarly abbreviate a public, etc. declaration with a definition:

 (define/public name expr)
=expands=>
 (begin
  (public name)
  (define name expr))

 (define/public (header . formals) expr)
=expands=>
 (begin
  (public name)
  (define (header . formals) expr))

etc.

4.3.1  Initialization Variables

A class's initialization variables, declared with init, init-field, and init-rest, are instantiated for each object of a class. Initialization variables can be used in the initial value expressions of fields, default value expressions for initialization arguments, and in initialization expressions. Only initialization variables declared with init-field can be accessed from methods; accessing any other initialization variable from a method is a syntax error.

The values bound to initialization variables are

If an initialization argument is not provided for an initialization variable that has an associated default-value-expr, then the default-value-expr expression is evaluated to obtain a value for the variable. A default-value-expr is only evaluated when an argument is not provided for its variable. The environment of default-value-expr includes all of the initialization variables, all of the fields, and all of the methods of the class. If multiple default-value-exprs are evaluated, they are evaluated from left to right. Object creation and field initialization are described in detail in section 4.4.

If an initialization variable has no default-value-expr, then the object creation or superclass initialization call must supply an argument for the variable, otherwise the exn:fail:object exception is raised.

Initialization arguments can be provided by name or by position. The external name of an initialization variable can be used with instantiate or with the superclass initialization form. Those forms also accept by-position arguments. The make-object procedure and the superclass initialization procedure accept only by-position arguments.

Arguments provided by position are converted into by-name arguments using the order of init and init-field clauses and the order of variables within each clause. When a instantiate form provides both by-position and by-name arguments, the converted arguments are placed before by-name arguments. (The order can be significant; see also section 4.4.)

Unless a class contains an init-rest clause, when the number of by-position arguments exceeds the number of declared initialization variables, the order of variables in the superclass (and so on, up the superclass chain) determines the by-name conversion.

If a class expression contains an init-rest clause, there must be only one, and it must be last. If it declares a variable, then the variable receives extra by-position initialization arguments as a list (similar to a dotted ``rest argument'' in a procedure). An init-rest variable can receive by-position initialization arguments that are left over from a by-name conversion for a derived class. When a derived class's superclass initialization provides even more by-position arguments, they are prefixed onto the by-position arguments accumulated so far.

If too few or too many by-position initialization arguments are provided to an object creation or superclass initialization, then the exn:fail:object exception is raised. Similarly, if extra by-position arguments are provided to a class with an init-rest clause, the exn:fail:object exception is raised.

Unused (by-name) arguments are be propagated to the superclass, as described in section 4.4. Multiple initialization arguments can use the same name if the class derivation contains multiple declarations (in different classes) of initialization variables with the name. See section 4.4 for further details.

See also section 4.3.3.3 for information about internal and external names.

4.3.2  Fields

Each field, init-field, and non-method define-values clause in a class declares one or more new fields for the class. Fields declared with field or init-field are public. Public fields can be accessed and mutated by subclasses using inherit-field. Public fields are also accessible outside the class via class-field-accessor and mutable via class-field-mutator (see section 4.5). Fields declared with define-values are accessible only within the class.

A field declared with init-field is both a public field and an initialization variable. See section 4.3.1 for information about initialization variables.

An inherit-field declaration makes a public field defined by a superclass directly accessible in the class expression. If the indicated field is not defined in the superclass, the exn:fail:object exception is raised when the class expression is evaluated. Every field in a superclass is present in a derived class, even if it is not declared with inherit-field in the derived class. The inherit-field clause does not control inheritance, but merely controls lexical scope within a class expression.

When an object is first created, all of its fields have the undefined value (see section 3.1 in PLT MzScheme: Language Manual). The fields of a class are initialized at the same time that the class's initialization expressions are evaluated; see section 4.4 for more information.

See also section 4.3.3.3 for information about internal and external names.

4.3.3  Methods

4.3.3.1  Method Definitions

Each public, override, augment, pubment, overment, augride, public-final, override-final, augment-final, and private clause in a class declares one or more method names. Each method name must have a corresponding method-definition. The order of public, etc. clauses and their corresponding definitions (among themselves, and with respect to other clauses in the class) does not matter.

As shown in section 4.3, a method definition is syntactically restricted to certain procedure forms, as defined by the grammar for method-procedure; in the last two forms of method-procedure, the body identifier must be one of the identifiers bound by let-values or letrec-values. A method-procedure expression is not evaluated directly. Instead, for each method, a class-specific method procedure is created; it takes an initial object argument, in addition to the arguments the procedure would accept if the method-procedure expression were evaluated directly. The body of the procedure is transformed to access methods and fields through the object argument.

A method declared with public, pubment, or public-final introduces a new method into a class. The method must not be present already in the superclass, otherwise the exn:fail:object exception is raised when the class expression is evaluated. A method declared with public can be overridden in a subclass that uses override, overment, or override-final. A method declared with pubment can be augmented in a subclass that uses augment, augride, or augment-final. A method declared with public-final cannot be overridden or augmented in a subclass.

A method declared with override, overment, or override-final overrides a definition already present in the superclass. If the method is not already present, the exn:fail:object exception is raised when the class expression is evaluated. A method declared with override can be overridden again in a subclass that uses override, overment, or override-final. A method declared with overment can be augmented in a subclass that uses augment, augride, or augment-final. A method declared with override-final cannot be overridden further or augmented in a subclass.

A method declared with augment, augride, or augment-final augments a definition already present in the superclass. If the method is not already present, the exn:fail:object exception is raised when the class expression is evaluated. A method declared with augment can be augmented further in a subclass that uses augment, augride, or augment-final. A method declared with augride can be overridden in a subclass that uses override, overment, or override-final. (Such an override merely replaces the augmentation, not the method that is augmented.) A method declared with augment-final cannot be overridden or augmented further in a subclass.

A method declared with private is not accessible outside the class expression, cannot be overridden, and never overrides a method in the superclass.

When a method is declared with override, overment, or override-final, then the superclass implementation of the method can be called using super form:

(super identifier arg-expr ···)

Such a super call always accesses the superclass method, independent of whether the method is overridden again in subclasses.

When a method is declared with pubment, augment, or overment, then a subclass augmenting method can be called using the inner form:

(inner default-expr identifier arg-expr ···)

If the object's class does not supply an augmenting method, then default-expr is evaluated. Otherwise, the augmenting method is called with the arg-expr results as arguments. If no inner call is evaluated for a particular method, then augmenting methods supplied by subclasses are never used. (The only difference between public-final and pubment without a corresponding inner is that public-final prevents the declaration of augmenting methods that would be ignored.)

4.3.3.2  Inherited and Superclass Methods

Each inherit, rename-super, and rename-inner clause declares one or more methods that are defined in the class, but must be present in the superclass. The rename-super and rename-inner declarations are rarely used, since superclass and augmenting methods are typically accessed through super and inner in a class that also declares the methods.

Methods declared with inherit access overriding declarations, if any, at run time. Methods declared with rename-super always access the superclass's implementation at run-time. Methods declared with rename-inner access a subclass's augmenting method, if any, and must be called with the form

(identifier (lambda () default-expr) arg-expr ···)

so that a default-expr is available to evaluate when no augmenting method is available. In such a form, lambda is a keyword to separate the default-expr from the arg-expr. When an augmenting method is available, it receives the results of the arg-exprs as arguments.

Methods that are present in the superclass but not declared with inherit or rename-super are not directly accessible in the class (through they can be called with send). Every public method in a superclass is present in a derived class, even if it is not declared with inherit in the derived class. The inherit clause does not control inheritance, but merely controls lexical scope within a class expression.

If a method declared with inherit, rename-super, or rename-inner is not present in the superclass, the exn:fail:object exception is raised when the class expression is evaluated.

4.3.3.3  Internal and External Names

Each method declared with public, override, augment, pubment, overment, augride, public-final, override-final, augment-final, inherit, rename-super, and rename-inner can have separate internal and external names when (internal-id external-id) is used for declaring the method. The internal name is used to access the method directly within the class expression (including within super or inner forms), while the external name is used with send and generic (see section 4.5). If a single identifier is provided for a method declaration, the identifier is used for both the internal and external names.

Method inheritance, overriding, and augmentation are based external names, only. Separate internal and external names are required for rename-super and rename-inner (for historical reasons, mainly).

Each init, init-field, field, or inherit-field variable similarly has an internal and an external name. The internal name is used within the class to access the variable, while the external name is used outside the class when providing initialization arguments (e.g., to instantiate), inheriting a field, or accessing a field externally (e.g., with class-field-accessor). As for methods, when inheriting a field with inherit-field, the external name is matched to an external field name in the superclass, while the internal name is bound in the class expression.

A single identifier can be used as an internal identifier and an external identifier, and it is possible to use the same identifier as internal and external identifiers for different bindings. Furthermore, within a single class, a single name can be used as an external method name, an external field name, and an external initialization argument name. Overall, each internal identifier must be distinct from all other internal identifiers, each external method name must be distinct from all other method names, each external field name must be distinct from all other field names, and each initialization argument name must be distinct from all other initialization argument names

By default, external names have no lexical scope, which means, for example, that an external method name matches the same syntactic symbol in all uses of send. The define-local-member-name form introduces a set of scoped external names:

(define-local-member-name identifier ···)

This form binds each identifier so that, within the scope of the definition, each use of each identifier as an external name is resolved to a hidden name generated by the define-local-member-name declaration. Thus, methods, fields, and initialization arguments declared with such external-name identifiers are accessible only in the scope of the define-local-member-name declaration.

The binding introduced by define-local-member-name is a syntax binding that can be exported and imported with modules (see section 5 in PLT MzScheme: Language Manual). Each execution of a define-local-member-name declaration generates a distinct hidden name. The interface->method-names procedure (see section 4.8) does not expose hidden names.

Example:

(define o (let ()
            (define-local-member-name m)
            (define c% (class object%
                         (define/public (m) 10)
                         (super-new))
            (define o (new c%))

            (send o m) ; => 10
            o))

(send o m) ; => error: no method m

4.4  Creating Objects

The make-object procedure creates a new object with by-position initialization arguments:

  (make-object class init-v ···)

An instance of class is created, and the init-vs are passed as initialization arguments, bound to the initialization variables of class for the newly created object as described in section 4.3.1. If class is not a class, the exn:fail:contract exception is raised.

The new form creates a new object with by-name initialization arguments:

(new class-expr (identifier by-name-expr) ···)

An instance of the value of class-expr is created, and the value of each by-name-expr is provided as a by-name argument for the corresponding identifier.

The instantiate form creates a new object with both by-position and by-name initialization arguments:

(instantiate class-expr (by-pos-expr ···) (identifier by-name-expr) ···)

An instance of the value of class-expr is created, and the values of the by-pos-exprs are provided as by-position initialization arguments. In addition, the value of each by-name-expr is provided as a by-name argument for the corresponding identifier.

All fields in the newly created object are initially bound to the special undefined value (see section 3.1 in PLT MzScheme: Language Manual). Initialization variables with default value expressions (and no provided value) are also initialized to undefined. After argument values are assigned to initialization variables, expressions in field clauses, init-field clauses with no provided argument, init clauses with no provided argument, private field definitions, and other expressions are evaluated. Those expressions are evaluated as they appear in the class expression, from left to right.

Sometime during the evaluation of the expressions, superclass-declared initializations must be executed once by using the super-instantiate form:

(super-instantiate (by-position-super-init-expr ···) (identifier by-name-super-init-expr ···) ···)

or by using the procedure produced by the super-make-object form:

(super-make-object super-init-v ···)

or by using super-new form:

(super-new (identifier by-name-super-init-expr ···) ···)

The by-position-super-init-exprs, by-name-super-init-exps, and super-init-vs are mapped to initialization variables in the same way as for instantiate, make-object, and new.

By-name initialization arguments to a class that have no matching initialization variable are implicitly added as by-name arguments to a super-instantiate, super-make-object, or super-new invocation, after the explicit arguments. If multiple initialization arguments are provided for the same name, the first (if any) is used, and the unused arguments are propagated to the superclass. (Note that converted by-position arguments are always placed before explicit by-name arguments.) The initialization procedure for the object% class accepts zero initialization arguments; if it receives any by-name initialization arguments, then exn:fail:object exception is raised.

Fields inherited from a superclass will not be initialized until the superclass's initialization procedure is invoked. In contrast, all methods are available for an object as soon as the object is created; the overriding of methods is not affect by initialization (unlike objects in C++).

It is an error to reach the end of initialization for any class in the hierarchy without invoking superclasses initialization; the exn:fail:object exception is raised in such a case. Also, if superclass initialization is invoked more than once, the exn:fail:object exception is raised.

4.5  Field and Method Access

In expressions within a class definition, the initialization variables, fields, and methods of the class all part of the environment. Within a method body, only the fields and other methods of the class can be referenced; a reference to any other class-introduced identifier is a syntax error. Elsewhere within the class, all class-introduced identifiers are available, and fields and initialization variables can be mutated with set!.

4.5.1  Methods

Method names within a class can only be used in the procedure position of an application expression; any other use is a syntax error. To allow methods to be applied to lists of arguments, a method application can have the form

(method-id arg-expr ··· . arg-list-expr)
(super method-id arg-expr ··· . arg-list-expr)
(inner default-expr method-id arg-expr ··· . arg-list-expr)

which calls the method in a way analogous to (apply method-id arg-expr ··· arg-list-expr). The arg-list-expr must not be a parenthesized expression, otherwise the dot and the parentheses will cancel each other.

Methods are called from outside a class with the send and send/apply forms:

(send obj-expr method-name arg-expr ···)
(send obj-expr method-name arg-expr ··· . arg-list-expr)
(send/apply obj-expr method-name arg-expr ··· arg-list-expr)

where the last two forms apply the method to a list of argument values; in the second form, arg-list-expr cannot be a parenthesized expression. For any send or send/apply, if obj-expr does not produce an object, the exn:fail:contract exception is raised. If the object has no public method method-name, the exn:fail:object exception is raised.

The send* form calls multiple methods of an object in the specified order:

(send* obj-expr msg ···)

msg is one of
  (method-name arg-expr ···)
  (method-name arg-expr ··· . arg-list-expr)

where arg-list-expr is not a parenthesized expression.

Example:

(send* edit (begin-edit-sequence)
            (insert "Hello")
            (insert #\newline)
            (end-edit-sequence))

which is the same as

(let ([o edit])
  (send o begin-edit-sequence)
  (send o insert "Hello")
  (send o insert #\newline)
  (send o end-edit-sequence))

The with-method form extracts a method from an object and binds a local name that can be applied directly (in the same way as declared methods within a class):

(with-method ((identifier (object-expr method-name)) ···)
  expr ···1)

Example:

(let ([s (new stack%)])
  (with-method ([push (s push!)]
                [pop (s pop!)])
    (push 10)
    (push 9)
    (pop)))

which is the same as

(let ([s (new stack%)])
  (send s push! 10)
  (send s push! 9)
  (send s pop!))

4.5.2  Fields

The get-field form,

(get-field identifier object-expr)

extracts the field named by identifier from the value of the object-expr.

The field-bound? form,

(field-bound? identifier object-expr)

produces #t if object-expr evaluates to an object that has a field named identifier, #f otherwise.

If you have access to the class of an object, the class-field-accessor and class-field-mutator forms provide efficient access to the object's fields.

4.5.3  Generics

A generic can be used instead of a method name to avoid the cost of relocating a method by name within a class. The make-generic procedure and generic form create generics:

A generic is applied with send-generic:

(send-generic obj-expr generic-expr arg-expr ···)
(send-generic obj-expr generic-expr arg-expr ··· . arg-list-expr)

where the value of obj-expr is an object and the value of generic-expr is a generic.

4.6  Mixins

A mixin is a class parameterization modeled on a paper published by Flatt, Felleisen, and Krishnamurthi, available at http://www.ccs.neu.edu/scheme/pubs/#popl98-fkf.

The implementation of these mixins in MzScheme is with the combination of lambda and class. This macro simplifies the checking and implementation of these mixins. Its syntax is very similar to the syntax for class*. The shape of a mixin is:

(mixin (interface-expr ...) (interface-expr ...)
  class-clause ...)

This macro expands into a procedure that accepts a class. The argument passed to this procedure must match the interfaces of the first interface-exprs expressions. The procedure returns a class that is derived from its argument. This result class must match the interfaces specified in the second interface-exprs section; it has clauses specified by instance-variable-clauses. The syntax of the initialization-variables and instance-variable-clause are exactly the same as class*/names.

The mixin macro does some checking to be sure that variables that the instance-variable-clauses refer to in their super class are in the interfaces. That checking and the checking that the input class matches the declared interfaces aside, the mixin macro's expansion is something like this:

(mixin (i<%> ...) (j<%> ...)
  class-clause ...)
=
(lambda (%)
  (class* % (j<%> ...)
    class-clause ...))

The i<%> interfaces do not appear in the output because they are only used for the error checking and are discarded by the time the class is created.

4.7  Object Serialization

The define-serializable-class and define-serializable-class* forms define classes whose instances are serializable using serialize (see section 39).

(define-serializable-class class-id superclass-expr
  class-clause
  ···)

(define-serializable-class* class-id superclass-expr (interface-expr ···)
  class-clause
  ···)

These forms can only be used at the top level, either within a module or outside. The class-id identifier is bound to the new class, and deserialize-info:class-id is also defined; if the definition is within a module, then the latter is provided from the module. The superclass-expr, interface-exprs, and class-clauses are as for class and class* (see section 4.3).

Serialization for the class works in one of two ways:

In the second case, a serializable subclass can implement externalizable<%>, in which case the externalize method is responsible for all serialization (i.e., automatic serialization is lost for instances of the subclass). In the first case, all serializable subclasses implement externalizable<%>, since a subclass implements all of the interfaces of its parent class.

In either case, if an object is an immediate instance of a subclass (that is not itself serializable), the object is serialized as if it was an immediate instance of the serializable class. In particular, overriding declarations of the externalize method are ignored for instances of non-serializable subclasses.

4.8  Object, Class, and Interface Utilities

(object? v) returns #t if v is an object, #f otherwise.

(class? v) returns #t if v is a class, #f otherwise.

(interface? v) returns #t if v is an interface, #f otherwise.

(object=? object [object]) determines if two objects are the same object, or not (uses eq?, but also works properly with contracts).

(object->vector object [opaque-v]) returns a vector representing object that shows its inspectable fields, analogous to struct->vector (see section 4.8 in PLT MzScheme: Language Manual).

(class->interface class) returns the interface implicitly defined by class (see the overview at the beginning of Chapter 4).

(object-interface object) returns the interface implicitly defined by the class of object.

(is-a? v interface) returns #t if v is an instance of a class that implements interface, #f otherwise.

(is-a? v class) returns #t if v is an instance of class (or of a class derived from class), #f otherwise.

(subclass? v class) returns #t if v is a class derived from (or equal to) class, #f otherwise.

(implementation? v interface) returns #t if v is a class that implements interface, #f otherwise.

(interface-extension? v interface) returns #t if v is an interface that extends interface, #f otherwise.

(method-in-interface? symbol interface) returns #t if interface (or any of its ancestor interfaces) includes a member with the name symbol, #f otherwise.

(interface->method-names interface) returns a list of symbols for the method names in interface, including methods inherited from superinterfaces, but not including methods whose names are local (i.e., declared with define-local-member-names).

(object-method-arity-includes? object symbol k) returns #t if object has a method named symbol that accepts k arguments, #f otherwise.

(field-names object) returns a list of all of the names of the fields bound in object, including fields inherited from superinterfaces, but not including fields whose names are local (i.e., declared with define-local-member-names).

(object-info object) returns two values, analogous to the return values of struct-info (see section 4.5 in PLT MzScheme: Language Manual):

(class-info class) returns seven values, analogous to the return values of struct-type-info (see section 4.5 in PLT MzScheme: Language Manual):

4.9  Expanding to a Class Declaration

The class/derived form is like class*, but it includes a sub-expression to use used as the source for all syntax errors within the class definition. For example, define-serializable-class expands to class/derived so that error in the body of the class are reported in terms of define-serializable-class instead of class.

(class/derived original-datum
  (name-id super-expr (interface-expr ...) deserialize-id-expr)
  class-clause
  ···)

The original-datum is the original expression to use for reporting errors.

The name-id is used to name the resulting class; if it is #f, the class name is inferred.

The super-expr, interface-exprs, and class-clauses are as for class* (see section 4.3).

If the deserialize-id-expr is not literally #f, then a serializable class is generated, and the result is two values instead of one: the class and a deserialize-info structure produced by make-deserialize-info. The deserialize-id-expr should produce a value suitable as the second argument to make-serialize-info, and it should refer to an export whose value is the deserialize-info structure.

Future optional forms may be added to the sequence that currently ends with deserialize-id-expr.


1 A bracketed percent sign (``<%>'') is used by convention in MzScheme to indicate that a variable's value is an interface.

2 A percent sign (``%'') is used by convention in MzScheme to indicate that a variable's value is a class.