2780 lines
103 KiB
Racket
2780 lines
103 KiB
Racket
#lang scribble/doc
|
|
@(require "mz.rkt"
|
|
racket/class
|
|
(for-syntax racket/base racket/serialize racket/trait))
|
|
|
|
@(begin
|
|
|
|
(define-syntax sees
|
|
(syntax-rules ()
|
|
[(_) ""]
|
|
[(_ s) (elem " and " (secref s))]
|
|
[(_ s ... s0) (elem (elem ", " (secref s)) ... ", and " (secref s0))]))
|
|
|
|
(define-syntax (defclassforms stx)
|
|
(syntax-case stx (*)
|
|
[(_ [* (form ...) (also ...) more ...])
|
|
#'(defform* (form ...)
|
|
"See " @racket[class*] (sees also ...) "; use"
|
|
" outside the body of a " @racket[class*] " form is a syntax error."
|
|
more ...)]
|
|
[(_ [form (also ...) more ...])
|
|
#'(defclassforms [* (form) (also ...) more ...])]
|
|
[(_ form ...)
|
|
#'(begin (defclassforms form) ...)]))
|
|
|
|
(define-syntax (defstarshorthands stx)
|
|
(syntax-case stx ()
|
|
[(_ form)
|
|
(with-syntax ([name (string->symbol
|
|
(let ([s (symbol->string (syntax-e #'form))])
|
|
(substring s 0 (sub1 (string-length s)))))]
|
|
[tmpl (let ([s #'(... (thing (id expr) ...))])
|
|
(datum->syntax s
|
|
(cons (datum->syntax
|
|
#'form
|
|
(syntax-e #'form)
|
|
(car (syntax-e s)))
|
|
(cdr (syntax-e s)))
|
|
s))])
|
|
#'(...
|
|
(defform tmpl
|
|
"Shorthand for " (racket (begin (#,(racket name) id) ... (define id _expr) ...)) ".")))]
|
|
[(_ form ...)
|
|
#'(begin (defstarshorthands form) ...)]))
|
|
|
|
(define-syntax (defdefshorthands stx)
|
|
(syntax-case stx ()
|
|
[(_ form)
|
|
(with-syntax ([name (string->symbol
|
|
(let ([s (symbol->string (syntax-e #'form))])
|
|
(string-append "define/" s)))])
|
|
(with-syntax ([tmpl1 (let ([s #'(define id expr)])
|
|
(datum->syntax s
|
|
(cons (datum->syntax
|
|
#'form
|
|
(syntax-e #'name)
|
|
(car (syntax-e s)))
|
|
(cdr (syntax-e s)))
|
|
s))])
|
|
#'(...
|
|
(defform* [tmpl1 (#,(racket name) (id . formals) body ...+)]
|
|
"Shorthand for "
|
|
(racket (begin (#,(racket form) id) (define id expr)))
|
|
" or "
|
|
(racket (begin (#,(racket form) id) (define (id . formals) body ...+)))))))]
|
|
[(_ form ...)
|
|
#'(begin (defdefshorthands form) ...)]))
|
|
|
|
(define class-eval (make-base-eval))
|
|
(define class-ctc-eval (make-base-eval))
|
|
|
|
)
|
|
|
|
@examples[#:hidden #:eval class-eval
|
|
(require racket/class racket/contract)]
|
|
@examples[#:hidden #:eval class-ctc-eval
|
|
(require racket/class racket/contract)]
|
|
|
|
@title[#:tag "mzlib:class" #:style 'toc]{Classes and Objects}
|
|
|
|
@guideintro["classes"]{classes and objects}
|
|
|
|
@note-lib[racket/class #:use-sources (racket/private/class-internal)]
|
|
|
|
A @deftech{class} specifies
|
|
|
|
@itemize[
|
|
|
|
@item{a collection of fields;}
|
|
|
|
@item{a collection of methods;}
|
|
|
|
@item{initial value expressions for the fields; and}
|
|
|
|
@item{initialization variables that are bound to initialization
|
|
arguments.}
|
|
|
|
]
|
|
|
|
In the context of the class system, an @defterm{object} is a
|
|
collection of bindings for fields that are instantiated according to a
|
|
class description.
|
|
|
|
The class system allows a program to define a new class (a
|
|
@deftech{derived class}) in terms of an existing class (the
|
|
@deftech{superclass}) using inheritance, overriding, and augmenting:
|
|
|
|
@itemize[
|
|
|
|
@item{@deftech{inheritance}: An object of a derived class supports
|
|
methods and instantiates fields declared by the derived class's
|
|
superclass, as well as methods and fields declared in the derived
|
|
class expression.}
|
|
|
|
@item{@deftech{overriding}: Some methods declared in a superclass can
|
|
be replaced in the derived class. References to the overridden method
|
|
in the superclass use the implementation in the derived class.}
|
|
|
|
@item{@deftech{augmenting}: Some methods declared in a superclass can
|
|
be merely extended in the derived class. The superclass method
|
|
specifically delegates to the augmenting method in the derived class.}
|
|
|
|
]
|
|
|
|
An @deftech{interface} is a collection of method names to be
|
|
implemented by a class, combined with a derivation requirement. A
|
|
class @deftech{implements} an interface when it
|
|
|
|
@itemize[
|
|
|
|
@item{declares (or inherits) a public method for each variable in the
|
|
interface;}
|
|
|
|
@item{is derived from the class required by the interface, if any; and}
|
|
|
|
@item{specifically declares its intention to implement the interface.}
|
|
|
|
]
|
|
|
|
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 @deftech{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 values. However, a class or
|
|
interface is not an object (i.e., there are no ``meta-classes'' or
|
|
``meta-interfaces'').
|
|
|
|
@local-table-of-contents[]
|
|
|
|
@; ------------------------------------------------------------------------
|
|
|
|
@section[#:tag "createinterface"]{Creating Interfaces}
|
|
|
|
@guideintro["classes"]{classes, objects, and interfaces}
|
|
|
|
@defform/subs[(interface (super-interface-expr ...) name-clause ...)
|
|
([name-clause
|
|
id
|
|
(id contract-expr)])]{
|
|
|
|
Produces an interface. The @racket[id]s must be mutually distinct.
|
|
|
|
Each @racket[super-interface-expr] is evaluated (in order) when the
|
|
@racket[interface] expression is evaluated. The result of each
|
|
@racket[super-interface-expr] must be an interface value, otherwise
|
|
the @exnraise[exn:fail:object]. The interfaces returned by the
|
|
@racket[super-interface-expr]s 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 @racket[interface] expression is an interface that
|
|
includes all of the specified @racket[id]s, plus all identifiers from
|
|
the superinterfaces. Duplicate identifier names among the
|
|
superinterfaces are ignored, but if a superinterface contains one of
|
|
the @racket[id]s in the @racket[interface] expression, the
|
|
@exnraise[exn:fail:object]. A given @racket[id] may be paired with
|
|
a corresponding @racket[contract-expr].
|
|
|
|
If no @racket[super-interface-expr]s are provided, then the derivation
|
|
requirement of the resulting interface is trivial: any class that
|
|
implements the interface must be derived from @racket[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
|
|
@exnraise[exn:fail:object].
|
|
|
|
@examples[
|
|
#:eval class-ctc-eval
|
|
#:no-prompt
|
|
(define file-interface<%>
|
|
(interface () open close read-byte write-byte))
|
|
(define directory-interface<%>
|
|
(interface (file-interface<%>)
|
|
[file-list (->m (listof (is-a?/c file-interface<%>)))]
|
|
parent-directory))
|
|
]}
|
|
|
|
@defform/subs[(interface* (super-interface-expr ...)
|
|
([property-expr val-expr] ...)
|
|
name-clause ...)
|
|
([name-clause
|
|
id
|
|
(id contract-expr)])]{
|
|
|
|
Like @racket[interface], but also associates to the interface the
|
|
structure-type properties produced by the @racket[property-expr]s with
|
|
the corresponding @racket[val-expr]s.
|
|
|
|
Whenever the resulting interface (or a sub-interface derived from it)
|
|
is explicitly implemented by a class through the @racket[class*] form,
|
|
each property is attached with its value to a structure type that
|
|
instantiated by instances of the class. Specifically, the property is
|
|
attached to a structure type with zero immediate fields, which is
|
|
extended to produce the internal structure type for instances of the
|
|
class (so that no information about fields is accessible to the
|
|
structure type property's guard, if any).
|
|
|
|
@examples[
|
|
#:eval class-eval
|
|
#:no-prompt
|
|
(define i<%> (interface* () ([prop:custom-write
|
|
(lambda (obj port mode) (void))])
|
|
method1 method2 method3))
|
|
]}
|
|
|
|
@; ------------------------------------------------------------------------
|
|
|
|
@section[#:tag "createclass"]{Creating Classes}
|
|
|
|
@guideintro["classes"]{classes and objects}
|
|
|
|
@defthing[object% class?]{
|
|
|
|
A built-in class that has no methods fields, implements only its own
|
|
interface @racket[(class->interface object%)], and is transparent
|
|
(i.e,. its inspector is @racket[#f], so all immediate instances are
|
|
@racket[equal?]). All other classes are derived from @racket[object%].}
|
|
|
|
|
|
@defform/subs[
|
|
#:literals (inspect init init-field field inherit-field init-rest init-rest
|
|
public pubment public-final override override-final overment augment augride
|
|
augment-final private abstract inherit inherit/super inherit/inner
|
|
rename-super rename-inner begin lambda case-lambda let-values letrec-values
|
|
define-values #%plain-lambda chaperone-procedure)
|
|
(class* superclass-expr (interface-expr ...)
|
|
class-clause
|
|
...)
|
|
([class-clause
|
|
(inspect inspector-expr)
|
|
(init init-decl ...)
|
|
(init-field init-decl ...)
|
|
(field field-decl ...)
|
|
(inherit-field maybe-renamed ...)
|
|
(init-rest id)
|
|
(init-rest)
|
|
(public maybe-renamed ...)
|
|
(pubment maybe-renamed ...)
|
|
(public-final maybe-renamed ...)
|
|
(override maybe-renamed ...)
|
|
(overment maybe-renamed ...)
|
|
(override-final maybe-renamed ...)
|
|
(augment maybe-renamed ...)
|
|
(augride maybe-renamed ...)
|
|
(augment-final maybe-renamed ...)
|
|
(private id ...)
|
|
(abstract id ...)
|
|
(inherit maybe-renamed ...)
|
|
(inherit/super maybe-renamed ...)
|
|
(inherit/inner maybe-renamed ...)
|
|
(rename-super renamed ...)
|
|
(rename-inner renamed ...)
|
|
method-definition
|
|
definition
|
|
expr
|
|
(begin class-clause ...)]
|
|
|
|
[init-decl
|
|
id
|
|
(renamed)
|
|
(maybe-renamed default-value-expr)]
|
|
|
|
[field-decl
|
|
(maybe-renamed default-value-expr)]
|
|
|
|
[maybe-renamed
|
|
id
|
|
renamed]
|
|
|
|
[renamed
|
|
(internal-id external-id)]
|
|
|
|
[method-definition
|
|
(define-values (id) method-procedure)]
|
|
|
|
[method-procedure
|
|
(lambda kw-formals expr ...+)
|
|
(case-lambda (formals expr ...+) ...)
|
|
(#%plain-lambda formals expr ...+)
|
|
(let-values ([(id) method-procedure] ...)
|
|
method-procedure)
|
|
(letrec-values ([(id) method-procedure] ...)
|
|
method-procedure)
|
|
(let-values ([(id) method-procedure] ...+)
|
|
id)
|
|
(letrec-values ([(id) method-procedure] ...+)
|
|
id)
|
|
(chaperone-procedure method-procedure wrapper-proc
|
|
other-arg-expr ...)])]{
|
|
|
|
Produces a class value.
|
|
|
|
The @racket[superclass-expr] expression is evaluated when the
|
|
@racket[class*] expression is evaluated. The result must be a class
|
|
value (possibly @racket[object%]), otherwise the
|
|
@exnraise[exn:fail:object]. The result of the
|
|
@racket[superclass-expr] expression is the new class's superclass.
|
|
|
|
The @racket[interface-expr] expressions are also evaluated when the
|
|
@racket[class*] expression is evaluated, after
|
|
@racket[superclass-expr] is evaluated. The result of each
|
|
@racket[interface-expr] must be an interface value, otherwise the
|
|
@exnraise[exn:fail:object]. The interfaces returned by the
|
|
@racket[interface-expr]s 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
|
|
@exnraise[exn:fail:object]. The class's superclass must satisfy the
|
|
implementation requirement of each interface, otherwise the
|
|
@exnraise[exn:fail:object].
|
|
|
|
An @racket[inspect] @racket[class-clause] selects an inspector (see
|
|
@secref["inspectors"]) for the class extension. The
|
|
@racket[inspector-expr] must evaluate to an inspector or @racket[#f]
|
|
when the @racket[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 @racket[equal?]. If
|
|
no @racket[inspect] clause is provided, access to the class is
|
|
controlled by the parent of the current inspector (see
|
|
@secref["inspectors"]). A syntax error is reported if more than one
|
|
@racket[inspect] clause is specified.
|
|
|
|
The other @racket[class-clause]s define initialization arguments,
|
|
public and private fields, and public and private methods. For each
|
|
@racket[id] or @racket[maybe-renamed] in a @racket[public],
|
|
@racket[override], @racket[augment], @racket[pubment],
|
|
@racket[overment], @racket[augride], @racket[public-final],
|
|
@racket[override-final], @racket[augment-final], or @racket[private]
|
|
clause, there must be one @racket[method-definition]. All other
|
|
definition @racket[class-clause]s create private fields. All remaining
|
|
@racket[expr]s are initialization expressions to be evaluated when the
|
|
class is instantiated (see @secref["objcreation"]).
|
|
|
|
The result of a @racket[class*] expression is a new class, derived
|
|
from the specified superclass and implementing the specified
|
|
interfaces. Instances of the class are created with the
|
|
@racket[instantiate] form or @racket[make-object] procedure, as
|
|
described in @secref["objcreation"].
|
|
|
|
Each @racket[class-clause] is (partially) macro-expanded to reveal its
|
|
shapes. If a @racket[class-clause] is a @racket[begin] expression, its
|
|
sub-expressions are lifted out of the @racket[begin] and treated as
|
|
@racket[class-clause]s, in the same way that @racket[begin] is
|
|
flattened for top-level and embedded definitions.
|
|
|
|
Within a @racket[class*] form for instances of the new class,
|
|
@racket[this] is bound to the object itself;
|
|
@racket[this%] is bound to the class of the object;
|
|
@racket[super-instantiate], @racket[super-make-object], and
|
|
@racket[super-new] are bound to forms to initialize fields in the
|
|
superclass (see @secref["objcreation"]); @racket[super] is
|
|
available for calling superclass methods (see
|
|
@secref["clmethoddefs"]); and @racket[inner] is available for
|
|
calling subclass augmentations of methods (see
|
|
@secref["clmethoddefs"]).}
|
|
|
|
@defform[(class superclass-expr class-clause ...)]{
|
|
|
|
Like @racket[class*], but omits the @racket[_interface-expr]s, for the case that none are needed.
|
|
|
|
@examples[
|
|
#:eval class-eval
|
|
#:no-prompt
|
|
(define book-class%
|
|
(class object%
|
|
(field (pages 5))
|
|
(define/public (letters)
|
|
(* pages 500))
|
|
(super-new)))
|
|
]}
|
|
|
|
@defidform[this]{
|
|
|
|
@index['("self")]{Within} a @racket[class*] form, @racket[this] refers
|
|
to the current object (i.e., the object being initialized or whose
|
|
method was called). Use outside the body of a @racket[class*] form is
|
|
a syntax error.
|
|
|
|
@examples[
|
|
#:eval class-eval
|
|
(eval:no-prompt
|
|
(define (describe obj)
|
|
(printf "Hello ~a\n" obj))
|
|
(define table%
|
|
(class object%
|
|
(define/public (describe-self)
|
|
(describe this))
|
|
(super-new))))
|
|
(send (new table%) describe-self)
|
|
]}
|
|
|
|
@defidform[this%]{
|
|
|
|
Within a @racket[class*] form, @racket[this%] refers to the class
|
|
of the current object (i.e., the object being initialized or whose
|
|
method was called). Use outside the body of a @racket[class*] form is
|
|
a syntax error.
|
|
|
|
@examples[
|
|
#:eval class-eval
|
|
(eval:no-prompt
|
|
(define account%
|
|
(class object%
|
|
(super-new)
|
|
(init-field balance)
|
|
(define/public (add n)
|
|
(new this% [balance (+ n balance)]))))
|
|
(define savings%
|
|
(class account%
|
|
(super-new)
|
|
(inherit-field balance)
|
|
(define interest 0.04)
|
|
(define/public (add-interest)
|
|
(send this add (* interest balance))))))
|
|
(let* ([acct (new savings% [balance 500])]
|
|
[acct (send acct add 500)]
|
|
[acct (send acct add-interest)])
|
|
(printf "Current balance: ~a\n" (get-field balance acct)))
|
|
]}
|
|
|
|
@defclassforms[
|
|
[(inspect inspector-expr) ()]
|
|
[(init init-decl ...) ("clinitvars")
|
|
@examples[#:eval class-eval
|
|
(class object%
|
|
(super-new)
|
|
(init turnip
|
|
[(internal-potato potato)]
|
|
[carrot 'good]
|
|
[(internal-rutabaga rutabaga) 'okay]))]]
|
|
[(init-field init-decl ...) ("clinitvars" "clfields")
|
|
@examples[#:eval class-eval
|
|
(class object%
|
|
(super-new)
|
|
(init-field turkey
|
|
[(internal-ostrich ostrich)]
|
|
[chicken 7]
|
|
[(internal-emu emu) 13]))]]
|
|
[(field field-decl ...) ("clfields")
|
|
@examples[#:eval class-eval
|
|
(class object%
|
|
(super-new)
|
|
(field [minestrone 'ready]
|
|
[(internal-coq-au-vin coq-au-vin) 'stewing]))]]
|
|
[(inherit-field maybe-renamed ...) ("clfields")
|
|
@examples[#:eval class-eval
|
|
(eval:no-prompt
|
|
(define cookbook%
|
|
(class object%
|
|
(super-new)
|
|
(field [recipes '(caldo-verde oyakodon eggs-benedict)]
|
|
[pages 389]))))
|
|
(class cookbook%
|
|
(super-new)
|
|
(inherit-field recipes
|
|
[internal-pages pages]))]]
|
|
[* ((init-rest id) (init-rest)) ("clinitvars")
|
|
@examples[#:eval class-eval
|
|
(eval:no-prompt
|
|
(define fruit-basket%
|
|
(class object%
|
|
(super-new)
|
|
(init-rest fruits)
|
|
(displayln fruits))))
|
|
(make-object fruit-basket% 'kiwi 'lychee 'melon)]]
|
|
[(public maybe-renamed ...) ("clmethoddefs")
|
|
@examples[#:eval class-eval
|
|
(eval:no-prompt
|
|
(define jumper%
|
|
(class object%
|
|
(super-new)
|
|
(define (skip) 'skip)
|
|
(define (hop) 'hop)
|
|
(public skip [hop jump]))))
|
|
(send (new jumper%) skip)
|
|
(send (new jumper%) jump)]]
|
|
[(pubment maybe-renamed ...) ("clmethoddefs")
|
|
@examples[#:eval class-eval
|
|
(eval:no-prompt
|
|
(define runner%
|
|
(class object%
|
|
(super-new)
|
|
(define (run) 'run)
|
|
(define (trot) 'trot)
|
|
(pubment run [trot jog]))))
|
|
(send (new runner%) run)
|
|
(send (new runner%) jog)]]
|
|
[(public-final maybe-renamed ...) ("clmethoddefs")
|
|
@examples[#:eval class-eval
|
|
(eval:no-prompt
|
|
(define point%
|
|
(class object%
|
|
(super-new)
|
|
(init-field [x 0] [y 0])
|
|
(define (get-x) x)
|
|
(define (do-get-y) y)
|
|
(public-final get-x [do-get-y get-y]))))
|
|
(send (new point% [x 1] [y 3]) get-y)
|
|
(eval:error
|
|
(class point%
|
|
(super-new)
|
|
(define (get-x) 3.14)
|
|
(override get-x)))]]
|
|
[(override maybe-renamed ...) ("clmethoddefs")
|
|
@examples[#:eval class-eval
|
|
(eval:no-prompt
|
|
(define sheep%
|
|
(class object%
|
|
(super-new)
|
|
(define/public (bleat)
|
|
(displayln "baaaaaaaaah")))))
|
|
(eval:no-prompt
|
|
(define confused-sheep%
|
|
(class sheep%
|
|
(super-new)
|
|
(define (bleat)
|
|
(super bleat)
|
|
(displayln "???"))
|
|
(override bleat))))
|
|
(send (new sheep%) bleat)
|
|
(send (new confused-sheep%) bleat)]]
|
|
[(overment maybe-renamed ...) ("clmethoddefs")
|
|
@examples[#:eval class-eval
|
|
(eval:no-prompt
|
|
(define turkey%
|
|
(class object%
|
|
(super-new)
|
|
(define/public (gobble)
|
|
(displayln "gobble gobble")))))
|
|
(eval:no-prompt
|
|
(define extra-turkey%
|
|
(class turkey%
|
|
(super-new)
|
|
(define (gobble)
|
|
(super gobble)
|
|
(displayln "gobble gobble gobble")
|
|
(inner (void) gobble))
|
|
(overment gobble))))
|
|
(eval:no-prompt
|
|
(define cyborg-turkey%
|
|
(class extra-turkey%
|
|
(super-new)
|
|
(define/augment (gobble)
|
|
(displayln "110011111011111100010110001011011001100101")))))
|
|
(send (new extra-turkey%) gobble)
|
|
(send (new cyborg-turkey%) gobble)]]
|
|
[(override-final maybe-renamed ...) ("clmethoddefs")
|
|
@examples[#:eval class-eval
|
|
(eval:no-prompt
|
|
(define meeper%
|
|
(class object%
|
|
(super-new)
|
|
(define/public (meep)
|
|
(displayln "meep")))))
|
|
(eval:no-prompt
|
|
(define final-meeper%
|
|
(class meeper%
|
|
(super-new)
|
|
(define (meep)
|
|
(super meep)
|
|
(displayln "This meeping ends with me"))
|
|
(override-final meep))))
|
|
(send (new meeper%) meep)
|
|
(send (new final-meeper%) meep)]]
|
|
[(augment maybe-renamed ...) ("clmethoddefs")
|
|
@examples[#:eval class-eval
|
|
(eval:no-prompt
|
|
(define buzzer%
|
|
(class object%
|
|
(super-new)
|
|
(define/pubment (buzz)
|
|
(displayln "bzzzt")
|
|
(inner (void) buzz)))))
|
|
(eval:no-prompt
|
|
(define loud-buzzer%
|
|
(class buzzer%
|
|
(super-new)
|
|
(define (buzz)
|
|
(displayln "BZZZZZZZZZT"))
|
|
(augment buzz))))
|
|
(send (new buzzer%) buzz)
|
|
(send (new loud-buzzer%) buzz)]]
|
|
[(augride maybe-renamed ...) ("clmethoddefs")]
|
|
[(augment-final maybe-renamed ...) ("clmethoddefs")]
|
|
[(private id ...) ("clmethoddefs")
|
|
@examples[#:eval class-eval
|
|
(eval:no-prompt
|
|
(define light%
|
|
(class object%
|
|
(super-new)
|
|
(define on? #t)
|
|
(define (toggle) (set! on? (not on?)))
|
|
(private toggle)
|
|
(define (flick) (toggle))
|
|
(public flick))))
|
|
(eval:error (send (new light%) toggle))
|
|
(send (new light%) flick)]]
|
|
[(abstract id ...) ("clmethoddefs")
|
|
@examples[#:eval class-eval
|
|
(eval:no-prompt
|
|
(define train%
|
|
(class object%
|
|
(super-new)
|
|
(abstract get-speed)
|
|
(init-field [position 0])
|
|
(define/public (move)
|
|
(new this% [position (+ position (get-speed))])))))
|
|
(eval:no-prompt
|
|
(define acela%
|
|
(class train%
|
|
(super-new)
|
|
(define/override (get-speed) 241))))
|
|
(eval:no-prompt
|
|
(define talgo-350%
|
|
(class train%
|
|
(super-new)
|
|
(define/override (get-speed) 330))))
|
|
(eval:error (new train%))
|
|
(send (new acela%) move)]]
|
|
[(inherit maybe-renamed ...) ("classinherit")
|
|
@examples[#:eval class-eval
|
|
(eval:no-prompt
|
|
(define alarm%
|
|
(class object%
|
|
(super-new)
|
|
(define/public (alarm)
|
|
(displayln "beeeeeeeep")))))
|
|
(eval:no-prompt
|
|
(define car-alarm%
|
|
(class alarm%
|
|
(super-new)
|
|
(init-field proximity)
|
|
(inherit alarm)
|
|
(when (< proximity 10)
|
|
(alarm)))))
|
|
(new car-alarm% [proximity 5])]]
|
|
[(inherit/super maybe-renamed ...) ("classinherit")]
|
|
[(inherit/inner maybe-renamed ...) ("classinherit")]
|
|
[(rename-super renamed ...) ("classinherit")]
|
|
[(rename-inner renamed ...) ("classinherit")]
|
|
]
|
|
|
|
@defstarshorthands[
|
|
public*
|
|
pubment*
|
|
public-final*
|
|
override*
|
|
overment*
|
|
override-final*
|
|
augment*
|
|
augride*
|
|
augment-final*
|
|
private*
|
|
]
|
|
|
|
@defdefshorthands[
|
|
public pubment public-final override
|
|
overment override-final augment augride
|
|
augment-final private
|
|
]
|
|
|
|
|
|
@defform[
|
|
(class/derived original-datum
|
|
(name-id super-expr (interface-expr ...) deserialize-id-expr)
|
|
class-clause
|
|
...)
|
|
]{
|
|
|
|
Like @racket[class*], but includes a sub-expression to be used as the
|
|
source for all syntax errors within the class definition. For example,
|
|
@racket[define-serializable-class] expands to @racket[class/derived]
|
|
so that errors in the body of the class are reported in terms of
|
|
@racket[define-serializable-class] instead of @racket[class].
|
|
|
|
The @racket[original-datum] is the original expression to use for
|
|
reporting errors.
|
|
|
|
The @racket[name-id] is used to name the resulting class; if it
|
|
is @racket[#f], the class name is inferred.
|
|
|
|
The @racket[super-expr], @racket[interface-expr]s, and
|
|
@racket[class-clause]s are as for @racket[class*].
|
|
|
|
If the @racket[deserialize-id-expr] is not literally @racket[#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
|
|
@racket[make-deserialize-info]. The @racket[deserialize-id-expr]
|
|
should produce a value suitable as the second argument to
|
|
@racket[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 @racket[deserialize-id-expr].}
|
|
|
|
@; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
@subsection[#:tag "clinitvars"]{Initialization Variables}
|
|
|
|
A class's initialization variables, declared with @racket[init],
|
|
@racket[init-field], and @racket[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 @racket[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
|
|
|
|
@itemize[
|
|
|
|
@item{the arguments provided with @racket[instantiate] or passed to
|
|
@racket[make-object], if the object is created as a direct instance
|
|
of the class; or,}
|
|
|
|
@item{the arguments passed to the superclass initialization form or
|
|
procedure, if the object is created as an instance of a derived
|
|
class.}
|
|
|
|
]
|
|
|
|
If an initialization argument is not provided for an initialization
|
|
variable that has an associated @racket[_default-value-expr], then the
|
|
@racket[_default-value-expr] expression is evaluated to obtain a value
|
|
for the variable. A @racket[_default-value-expr] is only evaluated when
|
|
an argument is not provided for its variable. The environment of
|
|
@racket[_default-value-expr] includes all of the initialization
|
|
variables, all of the fields, and all of the methods of the class. If
|
|
multiple @racket[_default-value-expr]s are evaluated, they are
|
|
evaluated from left to right. Object creation and field initialization
|
|
are described in detail in @secref["objcreation"].
|
|
|
|
If an initialization variable has no @racket[_default-value-expr], then
|
|
the object creation or superclass initialization call must supply an
|
|
argument for the variable, otherwise the @exnraise[exn:fail:object].
|
|
|
|
Initialization arguments can be provided by name or by position. The
|
|
external name of an initialization variable can be used with
|
|
@racket[instantiate] or with the superclass initialization form. Those
|
|
forms also accept by-position arguments. The @racket[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 @racket[init] and @racket[init-field] clauses and
|
|
the order of variables within each clause. When an @racket[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 @secref["objcreation"].)
|
|
|
|
Unless a class contains an @racket[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 @racket[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
|
|
@racket[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
|
|
@exnraise[exn:fail:object]. Similarly, if extra by-position arguments
|
|
are provided to a class with an @racket[init-rest] clause, the
|
|
@exnraise[exn:fail:object].
|
|
|
|
Unused (by-name) arguments are to be propagated to the superclass, as
|
|
described in @secref["objcreation"]. 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 @secref["objcreation"] for further
|
|
details.
|
|
|
|
See also @secref["extnames"] for information about internal and
|
|
external names.
|
|
|
|
@; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
@subsection[#:tag "clfields"]{Fields}
|
|
|
|
Each @racket[field], @racket[init-field], and non-method
|
|
@racket[define-values] clause in a class declares one or more new
|
|
fields for the class. Fields declared with @racket[field] or
|
|
@racket[init-field] are public. Public fields can be accessed and
|
|
mutated by subclasses using @racket[inherit-field]. Public fields are
|
|
also accessible outside the class via @racket[class-field-accessor]
|
|
and mutable via @racket[class-field-mutator] (see
|
|
@secref["ivaraccess"]). Fields declared with @racket[define-values]
|
|
are accessible only within the class.
|
|
|
|
A field declared with @racket[init-field] is both a public field and
|
|
an initialization variable. See @secref["clinitvars"] for
|
|
information about initialization variables.
|
|
|
|
An @racket[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
|
|
@exnraise[exn:fail:object] when the class expression is evaluated.
|
|
Every field in a superclass is present in a derived class, even if it
|
|
is not declared with @racket[inherit-field] in the derived class. The
|
|
@racket[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-const| value (see @secref["void"]). The fields of a
|
|
class are initialized at the same time that the class's initialization
|
|
expressions are evaluated; see @secref["objcreation"] for more
|
|
information.
|
|
|
|
See also @secref["extnames"] for information about internal and
|
|
external names.
|
|
|
|
@; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
@subsection[#:tag "clmethods"]{Methods}
|
|
|
|
@subsubsection[#:tag "clmethoddefs"]{Method Definitions}
|
|
|
|
Each @racket[public], @racket[override], @racket[augment],
|
|
@racket[pubment], @racket[overment], @racket[augride],
|
|
@racket[public-final], @racket[override-final],
|
|
@racket[augment-final], and @racket[private]
|
|
clause in a class declares one or more method names. Each method name
|
|
must have a corresponding @racket[_method-definition]. The order of
|
|
@racket[public], @|etc|, clauses and their corresponding definitions
|
|
(among themselves, and with respect to other clauses in the class)
|
|
does not matter.
|
|
|
|
As shown in the grammar for @racket[class*], a method definition is
|
|
syntactically restricted to certain procedure forms, as defined by the
|
|
grammar for @racket[_method-procedure]; in the last two forms of
|
|
@racket[_method-procedure], the body @racket[id] must be one of the
|
|
@racket[id]s bound by @racket[let-values] or @racket[letrec-values]. A
|
|
@racket[_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 @racket[_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 @racket[public], @racket[pubment], or
|
|
@racket[public-final] introduces a new method into a class. The method
|
|
must not be present already in the superclass, otherwise the
|
|
@exnraise[exn:fail:object] when the class expression is evaluated. A
|
|
method declared with @racket[public] can be overridden in a subclass
|
|
that uses @racket[override], @racket[overment], or
|
|
@racket[override-final]. A method declared with @racket[pubment] can
|
|
be augmented in a subclass that uses @racket[augment],
|
|
@racket[augride], or @racket[augment-final]. A method declared with
|
|
@racket[public-final] cannot be overridden or augmented in a subclass.
|
|
|
|
A method declared with @racket[override], @racket[overment], or
|
|
@racket[override-final] overrides a definition already present in the
|
|
superclass. If the method is not already present, the
|
|
@exnraise[exn:fail:object] when the class expression is evaluated. A
|
|
method declared with @racket[override] can be overridden again in a
|
|
subclass that uses @racket[override], @racket[overment], or
|
|
@racket[override-final]. A method declared with @racket[overment] can
|
|
be augmented in a subclass that uses @racket[augment],
|
|
@racket[augride], or @racket[augment-final]. A method declared with
|
|
@racket[override-final] cannot be overridden further or augmented in a
|
|
subclass.
|
|
|
|
A method declared with @racket[augment], @racket[augride], or
|
|
@racket[augment-final] augments a definition already present in the
|
|
superclass. If the method is not already present, the
|
|
@exnraise[exn:fail:object] when the class expression is evaluated. A
|
|
method declared with @racket[augment] can be augmented further in a
|
|
subclass that uses @racket[augment], @racket[augride], or
|
|
@racket[augment-final]. A method declared with @racket[augride] can be
|
|
overridden in a subclass that uses @racket[override],
|
|
@racket[overment], or @racket[override-final]. (Such an override
|
|
merely replaces the augmentation, not the method that is augmented.)
|
|
A method declared with @racket[augment-final] cannot be overridden or
|
|
augmented further in a subclass.
|
|
|
|
A method declared with @racket[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 @racket[override], @racket[overment],
|
|
or @racket[override-final], then the superclass implementation of the
|
|
method can be called using @racket[super] form.
|
|
|
|
When a method is declared with @racket[pubment], @racket[augment], or
|
|
@racket[overment], then a subclass augmenting method can be called
|
|
using the @racket[inner] form. The only difference between
|
|
@racket[public-final] and @racket[pubment] without a corresponding
|
|
@racket[inner] is that @racket[public-final] prevents the declaration
|
|
of augmenting methods that would be ignored.
|
|
|
|
A method declared with @racket[abstract] must be declared without
|
|
an implementation. Subclasses may implement abstract methods via the
|
|
@racket[override], @racket[overment], or @racket[override-final]
|
|
forms. Any class that contains or inherits any abstract methods is
|
|
considered abstract and cannot be instantiated.
|
|
|
|
@defform*[[(super id arg ...)
|
|
(super id arg ... . arg-list-expr)]]{
|
|
|
|
Always accesses the superclass method, independent of whether the
|
|
method is overridden again in subclasses. Using the @racket[super]
|
|
form outside of @racket[class*] is a syntax error. Each @racket[arg]
|
|
is as for @racket[#%app]: either @racket[_arg-expr] or
|
|
@racket[_keyword _arg-expr].
|
|
|
|
The second form is analogous to using @racket[apply] with a procedure;
|
|
the @racket[arg-list-expr] must not be a parenthesized expression.}
|
|
|
|
@defform*[[(inner default-expr id arg ...)
|
|
(inner default-expr id arg ... . arg-list-expr)]]{
|
|
|
|
If the object's class does not supply an augmenting method, then
|
|
@racket[default-expr] is evaluated, and the @racket[arg] expressions
|
|
are not evaluated. Otherwise, the augmenting method is called with the
|
|
@racket[arg] results as arguments, and @racket[default-expr] is not
|
|
evaluated. If no @racket[inner] call is evaluated for a particular
|
|
method, then augmenting methods supplied by subclasses are never
|
|
used. Using the @racket[inner] form outside of @racket[class*] is an
|
|
syntax error.
|
|
|
|
The second form is analogous to using @racket[apply] with a procedure;
|
|
the @racket[arg-list-expr] must not be a parenthesized expression.}
|
|
|
|
@; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
@subsubsection[#:tag "classinherit"]{Inherited and Superclass Methods}
|
|
|
|
Each @racket[inherit], @racket[inherit/super], @racket[inherit/inner],
|
|
@racket[rename-super], and @racket[rename-inner] clause declares one
|
|
or more methods that are defined in the class, but must be present in
|
|
the superclass. The @racket[rename-super] and @racket[rename-inner]
|
|
declarations are rarely used, since @racket[inherit/super] and
|
|
@racket[inherit/inner] provide the same access. Also, superclass and
|
|
augmenting methods are typically accessed through @racket[super] and
|
|
@racket[inner] in a class that also declares the methods, instead of
|
|
through @racket[inherit/super], @racket[inherit/inner],
|
|
@racket[rename-super], or @racket[rename-inner].
|
|
|
|
Method names declared with @racket[inherit], @racket[inherit/super],
|
|
or @racket[inherit/inner] access overriding declarations, if any, at
|
|
run time. Method names declared with @racket[inherit/super] can also
|
|
be used with the @racket[super] form to access the superclass
|
|
implementation, and method names declared with @racket[inherit/inner]
|
|
can also be used with the @racket[inner] form to access an augmenting
|
|
method, if any.
|
|
|
|
Method names declared with @racket[rename-super] always access the
|
|
superclass's implementation at run-time. Methods declared with
|
|
@racket[rename-inner] access a subclass's augmenting method, if any,
|
|
and must be called with the form
|
|
|
|
@racketblock[
|
|
(_id (lambda () _default-expr) _arg ...)
|
|
]
|
|
|
|
so that a @racket[default-expr] is available to evaluate when no
|
|
augmenting method is available. In such a form, @racket[lambda] is a
|
|
literal identifier to separate the @racket[default-expr] from the
|
|
@racket[arg]. When an augmenting method is available, it receives the
|
|
results of the @racket[arg] expressions as arguments.
|
|
|
|
Methods that are present in the superclass but not declared with
|
|
@racket[inherit], @racket[inherit/super], or @racket[inherit/inner] or
|
|
@racket[rename-super] are not directly accessible in the class
|
|
(though they can be called with @racket[send]). Every public method
|
|
in a superclass is present in a derived class, even if it is not
|
|
declared with @racket[inherit] in the derived class; the
|
|
@racket[inherit] clause does not control inheritance, but merely
|
|
controls lexical scope within a class expression.
|
|
|
|
If a method declared with @racket[inherit], @racket[inherit/super],
|
|
@racket[inherit/inner], @racket[rename-super], or
|
|
@racket[rename-inner] is not present in the superclass, the
|
|
@exnraise[exn:fail:object] when the class expression is evaluated.
|
|
|
|
@; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
@subsubsection[#:tag "extnames"]{Internal and External Names}
|
|
|
|
Each method declared with @racket[public], @racket[override],
|
|
@racket[augment], @racket[pubment], @racket[overment],
|
|
@racket[augride], @racket[public-final], @racket[override-final],
|
|
@racket[augment-final], @racket[inherit], @racket[inherit/super],
|
|
@racket[inherit/inner], @racket[rename-super], and
|
|
@racket[rename-inner] can have separate internal and external names
|
|
when @racket[(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 @racket[super] or
|
|
@racket[inner] forms), while the external name is used with
|
|
@racket[send] and @racket[generic] (see @secref["ivaraccess"]). If
|
|
a single @racket[id] is provided for a method declaration, the
|
|
identifier is used for both the internal and external names.
|
|
|
|
Method inheritance, overriding, and augmentation are based on external
|
|
names only. Separate internal and external names are required for
|
|
@racket[rename-super] and @racket[rename-inner] (for historical
|
|
reasons, mainly).
|
|
|
|
Each @racket[init], @racket[init-field], @racket[field], or
|
|
@racket[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 @racket[instantiate]),
|
|
inheriting a field, or accessing a field externally (e.g., with
|
|
@racket[class-field-accessor]). As for methods, when inheriting a
|
|
field with @racket[inherit-field], the external name is matched to an
|
|
external field name in the superclass, while the internal name is
|
|
bound in the @racket[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 @racket[send]. The
|
|
@racket[define-local-member-name] and @racket[define-member-name] forms
|
|
introduce scoped external names.
|
|
|
|
When a @racket[class] expression is compiled, identifiers used in
|
|
place of external names must be symbolically distinct (when the
|
|
corresponding external names are required to be distinct), otherwise a
|
|
syntax error is reported. When no external name is bound by
|
|
@racket[define-member-name], then the actual external names are
|
|
guaranteed to be distinct when @racket[class] expression is evaluated.
|
|
When any external name is bound by @racket[define-member-name], the
|
|
@exnraise[exn:fail:object] by @racket[class] if the actual external
|
|
names are not distinct.
|
|
|
|
|
|
@defform[(define-local-member-name id ...)]{
|
|
|
|
Unless it appears as the top-level definition, binds each @racket[id]
|
|
so that, within the scope of the definition, each use of each
|
|
@racket[id] as an external name is resolved to a hidden name generated
|
|
by the @racket[define-local-member-name] declaration. Thus, methods,
|
|
fields, and initialization arguments declared with such external-name
|
|
@racket[id]s are accessible only in the scope of the
|
|
@racket[define-local-member-name] declaration. As a top-level
|
|
definition, @racket[define-local-member-name] binds @racket[id] to its
|
|
symbolic form.
|
|
|
|
The binding introduced by @racket[define-local-member-name] is a
|
|
syntax binding that can be exported and imported with
|
|
@racket[module]s. Each evaluation of a
|
|
@racket[define-local-member-name] declaration generates a distinct
|
|
hidden name (except as a top-level definition). The
|
|
@racket[interface->method-names] procedure does not expose hidden
|
|
names.
|
|
|
|
@examples[
|
|
#:eval class-eval
|
|
(eval:no-prompt
|
|
(define-values (r o)
|
|
(let ()
|
|
(define-local-member-name m)
|
|
(define c% (class object%
|
|
(define/public (m) 10)
|
|
(super-new)))
|
|
(define o (new c%))
|
|
|
|
(values (send o m)
|
|
o))))
|
|
|
|
r
|
|
(eval:error (send o m))
|
|
]}
|
|
|
|
|
|
@defform[(define-member-name id key-expr)]{
|
|
|
|
Maps a single external name to an external name that is determined by
|
|
an expression. The value of @racket[key-expr] must be the result of either a
|
|
@racket[member-name-key] expression or a @racket[generate-member-key] call.}
|
|
|
|
|
|
@defform[(member-name-key identifier)]{
|
|
|
|
Produces a representation of the external name for @racket[id] in the
|
|
environment of the @racket[member-name-key] expression.}
|
|
|
|
@defproc[(generate-member-key) member-name-key?]{
|
|
|
|
Produces a hidden name, just like the binding for
|
|
@racket[define-local-member-name].}
|
|
|
|
@defproc[(member-name-key? [v any/c]) boolean?]{
|
|
|
|
Returns @racket[#t] for values produced by @racket[member-name-key]
|
|
and @racket[generate-member-key], @racket[#f]
|
|
otherwise.}
|
|
|
|
@defproc[(member-name-key=? [a-key member-name-key?] [b-key member-name-key?]) boolean?]{
|
|
|
|
Produces @racket[#t] if member-name keys @racket[a-key] and
|
|
@racket[b-key] represent the same external name, @racket[#f]
|
|
otherwise.}
|
|
|
|
|
|
@defproc[(member-name-key-hash-code [a-key member-name-key?]) integer?]{
|
|
|
|
Produces an integer hash code consistent with
|
|
@racket[member-name-key=?] comparisons, analogous to
|
|
@racket[equal-hash-code].}
|
|
|
|
@examples[
|
|
#:eval class-eval
|
|
(eval:no-prompt
|
|
(define (make-c% key)
|
|
(define-member-name m key)
|
|
(class object%
|
|
(define/public (m) 10)
|
|
(super-new))))
|
|
|
|
(send (new (make-c% (member-name-key m))) m)
|
|
(eval:error (send (new (make-c% (member-name-key p))) m))
|
|
(send (new (make-c% (member-name-key p))) p)
|
|
|
|
(eval:no-prompt
|
|
(define (fresh-c%)
|
|
(let ([key (generate-member-key)])
|
|
(values (make-c% key) key)))
|
|
|
|
(define-values (fc% key) (fresh-c%)))
|
|
|
|
(eval:error (send (new fc%) m))
|
|
(let ()
|
|
(define-member-name p key)
|
|
(send (new fc%) p))
|
|
]
|
|
|
|
|
|
@; ------------------------------------------------------------------------
|
|
|
|
@section[#:tag "objcreation"]{Creating Objects}
|
|
|
|
The @racket[make-object] procedure creates a new object with
|
|
by-position initialization arguments, the @racket[new] form
|
|
creates a new object with by-name initialization arguments, and
|
|
the @racket[instantiate] form creates a new object with both
|
|
by-position and by-name initialization arguments.
|
|
|
|
|
|
All fields in the newly created object are initially bound to the
|
|
special @|undefined-const| value (see
|
|
@secref["void"]). Initialization variables with default value
|
|
expressions (and no provided value) are also initialized to
|
|
@|undefined-const|. After argument values are assigned to
|
|
initialization variables, expressions in @racket[field] clauses,
|
|
@racket[init-field] clauses with no provided argument,
|
|
@racket[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 evaluated once by
|
|
using the @racket[super-make-object] procedure,
|
|
@racket[super-new] form, or @racket[super-instantiate] form.
|
|
|
|
By-name initialization arguments to a class that have no matching
|
|
initialization variable are implicitly added as by-name arguments
|
|
to a @racket[super-make-object], @racket[super-new], or
|
|
@racket[super-instantiate] 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
|
|
@racket[object%] class accepts zero initialization arguments; if
|
|
it receives any by-name initialization arguments, then
|
|
@exnraise[exn:fail:object].
|
|
|
|
If the end of initialization is reached for any class in the
|
|
hierarchy without invoking the superclass's initialization, the
|
|
@exnraise[exn:fail:object]. Also, if superclass initialization is
|
|
invoked more than once, the @exnraise[exn:fail:object].
|
|
|
|
Fields inherited from a superclass are not 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 affected by
|
|
initialization (unlike objects in C++).
|
|
|
|
|
|
|
|
@defproc[(make-object [class class?] [init-v any/c] ...) object?]{
|
|
|
|
Creates an instance of @racket[class]. The @racket[init-v]s are
|
|
passed as initialization arguments, bound to the initialization
|
|
variables of @racket[class] for the newly created object as
|
|
described in @secref["clinitvars"]. If @racket[class] is not a
|
|
class, the @exnraise[exn:fail:contract].}
|
|
|
|
@defform[(new class-expr (id by-name-expr) ...)]{
|
|
|
|
Creates an instance of the value of @racket[class-expr] (which
|
|
must be a class), and the value of each @racket[by-name-expr] is
|
|
provided as a by-name argument for the corresponding
|
|
@racket[id].}
|
|
|
|
@defform[(instantiate class-expr (by-pos-expr ...) (id by-name-expr) ...)]{
|
|
|
|
Creates an instance of the value of @racket[class-expr] (which
|
|
must be a class), and the values of the @racket[by-pos-expr]s are
|
|
provided as by-position initialization arguments. In addition,
|
|
the value of each @racket[by-name-expr] is provided as a by-name
|
|
argument for the corresponding @racket[id].}
|
|
|
|
@defidform[super-make-object]{
|
|
|
|
Produces a procedure that takes by-position arguments an invokes
|
|
superclass initialization. See @secref["objcreation"] for more
|
|
information.}
|
|
|
|
|
|
@defform[(super-instantiate (by-pos-expr ...) (id by-expr ...) ...)]{
|
|
|
|
|
|
Invokes superclass initialization with the specified by-position and
|
|
by-name arguments. See @secref["objcreation"] for more
|
|
information.}
|
|
|
|
|
|
@defform[(super-new (id by-name-expr ...) ...)]{
|
|
|
|
Invokes superclass initialization with the specified by-name
|
|
arguments. See @secref["objcreation"] for more information.}
|
|
|
|
@; ------------------------------------------------------------------------
|
|
|
|
@section[#:tag "ivaraccess"]{Field and Method Access}
|
|
|
|
In expressions within a class definition, the initialization
|
|
variables, fields, and methods of the class are 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 @racket[set!].
|
|
|
|
@; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
@subsection[#:tag "methodcalls"]{Methods}
|
|
|
|
Method names used 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 following form:
|
|
|
|
@specsubform[
|
|
(method-id arg ... . arg-list-expr)
|
|
]
|
|
|
|
This form calls the method in a way analogous to @racket[(apply
|
|
_method-id _arg ... _arg-list-expr)]. The @racket[arg-list-expr]
|
|
must not be a parenthesized expression.
|
|
|
|
Methods are called from outside a class with the @racket[send],
|
|
@racket[send/apply], and @racket[send/keyword-apply] forms.
|
|
|
|
@defform*[[(send obj-expr method-id arg ...)
|
|
(send obj-expr method-id arg ... . arg-list-expr)]]{
|
|
|
|
Evaluates @racket[obj-expr] to obtain an object, and calls the method
|
|
with (external) name @racket[method-id] on the object, providing the
|
|
@racket[arg] results as arguments. Each @racket[arg] is as for
|
|
@racket[#%app]: either @racket[_arg-expr] or @racket[_keyword
|
|
_arg-expr]. In the second form, @racket[arg-list-expr] cannot be a
|
|
parenthesized expression.
|
|
|
|
If @racket[obj-expr] does not produce an object, the
|
|
@exnraise[exn:fail:contract]. If the object has no public method named
|
|
@racket[method-id], the @exnraise[exn:fail:object].}
|
|
|
|
@defform[(send/apply obj-expr method-id arg ... arg-list-expr)]{
|
|
|
|
Like the dotted form of @racket[send], but @racket[arg-list-expr] can
|
|
be any expression.}
|
|
|
|
@defform[(send/keyword-apply obj-expr method-id
|
|
keyword-list-expr value-list-expr
|
|
arg ... arg-list-expr)]{
|
|
|
|
Like @racket[send/apply], but with expressions for keyword and
|
|
argument lists like @racket[keyword-apply].}
|
|
|
|
@defproc[(dynamic-send [obj object?]
|
|
[method-name symbol?]
|
|
[v any/c] ...
|
|
[#:<kw> kw-arg any/c] ...) any]{
|
|
|
|
Calls the method on @racket[obj] whose name matches
|
|
@racket[method-name], passing along all given @racket[v]s and
|
|
@racket[kw-arg]s.}
|
|
|
|
|
|
@defform/subs[(send* obj-expr msg ...+)
|
|
([msg (method-id arg ...)
|
|
(method-id arg ... . arg-list-expr)])]{
|
|
|
|
Calls multiple methods (in order) of the same object. Each
|
|
@racket[msg] corresponds to a use of @racket[send].
|
|
|
|
For example,
|
|
|
|
@racketblock[
|
|
(send* edit (begin-edit-sequence)
|
|
(insert "Hello")
|
|
(insert #\newline)
|
|
(end-edit-sequence))
|
|
]
|
|
|
|
is the same as
|
|
|
|
@racketblock[
|
|
(let ([o edit])
|
|
(send o begin-edit-sequence)
|
|
(send o insert "Hello")
|
|
(send o insert #\newline)
|
|
(send o end-edit-sequence))
|
|
]}
|
|
|
|
@defform/subs[(send+ obj-expr msg ...)
|
|
([msg (method-id arg ...)
|
|
(method-id arg ... . arg-list-expr)])]{
|
|
|
|
Calls methods (in order) starting with the object produced by
|
|
@racket[obj-expr]. Each method call will be invoked on the result of
|
|
the last method call, which is expected to be an object. Each
|
|
@racket[msg] corresponds to a use of @racket[send].
|
|
|
|
This is the functional analogue of @racket[send*].
|
|
|
|
@examples[#:eval class-eval
|
|
(eval:no-prompt
|
|
(define point%
|
|
(class object%
|
|
(super-new)
|
|
(init-field [x 0] [y 0])
|
|
(define/public (move-x dx)
|
|
(new this% [x (+ x dx)]))
|
|
(define/public (move-y dy)
|
|
(new this% [y (+ y dy)])))))
|
|
|
|
(send+ (new point%)
|
|
(move-x 5)
|
|
(move-y 7)
|
|
(move-x 12))
|
|
]}
|
|
|
|
@defform[(with-method ((id (obj-expr method-id)) ...)
|
|
body ...+)]{
|
|
|
|
Extracts methods from an object and binds a local name that can be
|
|
applied directly (in the same way as declared methods within a class)
|
|
for each method. Each @racket[obj-expr] must produce an object,
|
|
which must have a public method named by the corresponding
|
|
@racket[method-id]. The corresponding @racket[id] is bound so that it
|
|
can be applied directly (see @secref["methodcalls"]).
|
|
|
|
Example:
|
|
|
|
@racketblock[
|
|
(let ([s (new stack%)])
|
|
(with-method ([push (s push!)]
|
|
[pop (s pop!)])
|
|
(push 10)
|
|
(push 9)
|
|
(pop)))
|
|
]
|
|
|
|
is the same as
|
|
|
|
@racketblock[
|
|
(let ([s (new stack%)])
|
|
(send s push! 10)
|
|
(send s push! 9)
|
|
(send s pop!))
|
|
]}
|
|
|
|
@; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
@subsection{Fields}
|
|
|
|
@defform[(get-field id obj-expr)]{
|
|
|
|
Extracts the field with (external) name @racket[id] from the value of
|
|
@racket[obj-expr].
|
|
|
|
If @racket[obj-expr] does not produce an object, the
|
|
@exnraise[exn:fail:contract]. If the object has no @racket[id] field,
|
|
the @exnraise[exn:fail:object].}
|
|
|
|
@defproc[(dynamic-get-field [field-name symbol?] [obj object?]) any/c]{
|
|
|
|
Extracts the field from @racket[obj] with the (external) name that
|
|
matches @racket[field-name]. If the object has no field matching @racket[field-name],
|
|
the @exnraise[exn:fail:object].}
|
|
|
|
@defform[(set-field! id obj-expr expr)]{
|
|
|
|
Sets the field with (external) name @racket[id] from the value of
|
|
@racket[obj-expr] to the value of @racket[expr].
|
|
|
|
If @racket[obj-expr] does not produce an object, the
|
|
@exnraise[exn:fail:contract]. If the object has no @racket[id] field,
|
|
the @exnraise[exn:fail:object].}
|
|
|
|
@defproc[(dynamic-set-field! [field-name symbol?] [obj object?] [v any/c]) void?]{
|
|
|
|
Sets the field from @racket[obj] with the (external) name that
|
|
matches @racket[field-name] to @racket[v]. If the object has no field matching @racket[field-name],
|
|
the @exnraise[exn:fail:object].}
|
|
|
|
@defform[(field-bound? id obj-expr)]{
|
|
|
|
Produces @racket[#t] if the object result of @racket[obj-expr] has a
|
|
field with (external) name @racket[id], @racket[#f] otherwise.
|
|
|
|
If @racket[obj-expr] does not produce an object, the
|
|
@exnraise[exn:fail:contract].}
|
|
|
|
@defform[(class-field-accessor class-expr field-id)]{
|
|
|
|
Returns an accessor procedure that takes an instance of the class
|
|
produced by @racket[class-expr] and returns the value of the object's
|
|
field with (external) name @racket[field-id].
|
|
|
|
If @racket[class-expr] does not produce a class, the
|
|
@exnraise[exn:fail:contract]. If the class has no @racket[field-id]
|
|
field, the @exnraise[exn:fail:object].}
|
|
|
|
@defform[(class-field-mutator class-expr field-id)]{
|
|
|
|
Returns a mutator procedure that takes an instance of the class
|
|
produced by @racket[class-expr] and a value, and sets the value of the
|
|
object's field with (external) name @racket[field-id] to the given
|
|
value. The result is @|void-const|.
|
|
|
|
If @racket[class-expr] does not produce a class, the
|
|
@exnraise[exn:fail:contract]. If the class has no @racket[field-id]
|
|
field, the @exnraise[exn:fail:object].}
|
|
|
|
@; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
@subsection{Generics}
|
|
|
|
A @deftech{generic} can be used instead of a method name to avoid the
|
|
cost of relocating a method by name within a class.
|
|
|
|
@defform[(generic class-or-interface-expr id)]{
|
|
|
|
Produces a generic that works on instances of the class or interface
|
|
produced by @racket[class-or-interface-expr] (or an instance of a
|
|
class/interface derived from @racket[class-or-interface]) to call the
|
|
method with (external) name @racket[id].
|
|
|
|
If @racket[class-or-interface-expr] does not produce a class or
|
|
interface, the @exnraise[exn:fail:contract]. If the resulting class or
|
|
interface does not contain a method named @racket[id], the
|
|
@exnraise[exn:fail:object].}
|
|
|
|
@defform*[[(send-generic obj-expr generic-expr arg ...)
|
|
(send-generic obj-expr generic-expr arg ... . arg-list-expr)]]{
|
|
|
|
Calls a method of the object produced by @racket[obj-expr] as
|
|
indicated by the generic produced by @racket[generic-expr]. Each
|
|
@racket[arg] is as for @racket[#%app]: either @racket[_arg-expr] or
|
|
@racket[_keyword _arg-expr]. The second form is analogous to calling a
|
|
procedure with @racket[apply], where @racket[arg-list-expr] is not a
|
|
parenthesized expression.
|
|
|
|
If @racket[obj-expr] does not produce an object, or if
|
|
@racket[generic-expr] does not produce a generic, the
|
|
@exnraise[exn:fail:contract]. If the result of @racket[obj-expr] is
|
|
not an instance of the class or interface encapsulated by the result
|
|
of @racket[generic-expr], the @exnraise[exn:fail:object].}
|
|
|
|
@defproc[(make-generic [type (or/c class? interface?)]
|
|
[method-name symbol?])
|
|
generic?]{
|
|
|
|
Like the @racket[generic] form, but as a procedure that accepts a
|
|
symbolic method name.}
|
|
|
|
@; ------------------------------------------------------------------------
|
|
|
|
@section[#:tag "mixins"]{Mixins}
|
|
|
|
@defform[(mixin (interface-expr ...) (interface-expr ...)
|
|
class-clause ...)]{
|
|
|
|
Produces a @deftech{mixin}, which is a procedure that encapsulates a
|
|
class extension, leaving the superclass unspecified. Each time that a
|
|
mixin is applied to a specific superclass, it produces a new derived
|
|
class using the encapsulated extension.
|
|
|
|
The given class must implement interfaces produced by the first set of
|
|
@racket[interface-expr]s. The result of the procedure is a subclass
|
|
of the given class that implements the interfaces produced by the
|
|
second set of @racket[interface-expr]s. The @racket[class-clause]s are
|
|
as for @racket[class*], to define the class extension encapsulated by
|
|
the mixin.
|
|
|
|
Evaluation of a @racket[mixin] form checks that the
|
|
@racket[class-clause]s are consistent with both sets of
|
|
@racket[interface-expr]s.}
|
|
|
|
@; ------------------------------------------------------------------------
|
|
|
|
@section[#:tag "trait"]{Traits}
|
|
|
|
@note-lib-only[racket/trait]
|
|
|
|
A @deftech{trait} is a collection of methods that can be converted to
|
|
a @tech{mixin} and then applied to a @tech{class}. Before a trait is
|
|
converted to a mixin, the methods of a trait can be individually
|
|
renamed, and multiple traits can be merged to form a new trait.
|
|
|
|
@defform/subs[#:literals (public pubment public-final override override-final overment augment augride
|
|
augment-final private inherit inherit/super inherit/inner rename-super
|
|
field inherit-field)
|
|
|
|
(trait trait-clause ...)
|
|
([trait-clause (public maybe-renamed ...)
|
|
(pubment maybe-renamed ...)
|
|
(public-final maybe-renamed ...)
|
|
(override maybe-renamed ...)
|
|
(overment maybe-renamed ...)
|
|
(override-final maybe-renamed ...)
|
|
(augment maybe-renamed ...)
|
|
(augride maybe-renamed ...)
|
|
(augment-final maybe-renamed ...)
|
|
(inherit maybe-renamed ...)
|
|
(inherit/super maybe-renamed ...)
|
|
(inherit/inner maybe-renamed ...)
|
|
method-definition
|
|
(field field-declaration ...)
|
|
(inherit-field maybe-renamed ...)])]{
|
|
|
|
Creates a @tech{trait}. The body of a @racket[trait] form is similar to the
|
|
body of a @racket[class*] form, but restricted to non-private method
|
|
definitions. In particular, the grammar of
|
|
@racket[maybe-renamed], @racket[method-definition], and
|
|
@racket[field-declaration] are the same as for @racket[class*], and
|
|
every @racket[method-definition] must have a corresponding declaration
|
|
(one of @racket[public], @racket[override], etc.). As in
|
|
@racket[class], uses of method names in direct calls, @racket[super]
|
|
calls, and @racket[inner] calls depend on bringing method names into
|
|
scope via @racket[inherit], @racket[inherit/super],
|
|
@racket[inherit/inner], and other method declarations in the same
|
|
trait; an exception, compared to @racket[class] is that
|
|
@racket[overment] binds a method name only in the corresponding
|
|
method, and not in other methods of the same trait. Finally, macros
|
|
such as @racket[public*] and @racket[define/public] work in
|
|
@racket[trait] as in @racket[class].
|
|
|
|
External identifiers in @racket[trait], @racket[trait-exclude],
|
|
@racket[trait-exclude-field], @racket[trait-alias],
|
|
@racket[trait-rename], and @racket[trait-rename-field] forms are
|
|
subject to binding via @racket[define-member-name] and
|
|
@racket[define-local-member-name]. Although @racket[private] methods
|
|
or fields are not allowed in a @racket[trait] form, they can be
|
|
simulated by using a @racket[public] or @racket[field] declaration and
|
|
a name whose scope is limited to the @racket[trait] form.}
|
|
|
|
|
|
@defproc[(trait? [v any/c]) boolean?]{
|
|
|
|
Returns @racket[#t] if @racket[v] is a trait, @racket[#f] otherwise.}
|
|
|
|
|
|
@defproc[(trait->mixin [tr trait?]) (class? . -> . class?)]{
|
|
|
|
Converts a @tech{trait} to a @tech{mixin}, which can be applied to a
|
|
@tech{class} to produce a new @tech{class}. An expression of the form
|
|
|
|
@racketblock[
|
|
(trait->mixin
|
|
(trait
|
|
_trait-clause ...))
|
|
]
|
|
|
|
is equivalent to
|
|
|
|
@racketblock[
|
|
(lambda (%)
|
|
(class %
|
|
_trait-clause ...
|
|
(super-new)))
|
|
]
|
|
|
|
Normally, however, a trait's methods are changed and combined with
|
|
other traits before converting to a mixin.}
|
|
|
|
|
|
@defproc[(trait-sum [tr trait?] ...+) trait?]{
|
|
|
|
Produces a @tech{trait} that combines all of the methods of the given
|
|
@racket[tr]s. For example,
|
|
|
|
@racketblock[
|
|
(define t1
|
|
(trait
|
|
(define/public (m1) 1)))
|
|
(define t2
|
|
(trait
|
|
(define/public (m2) 2)))
|
|
(define t3 (trait-sum t1 t2))
|
|
]
|
|
|
|
creates a trait @racket[t3] that is equivalent to
|
|
|
|
@racketblock[
|
|
(trait
|
|
(define/public (m1) 1)
|
|
(define/public (m2) 2))
|
|
]
|
|
|
|
but @racket[t1] and @racket[t2] can still be used individually or
|
|
combined with other traits.
|
|
|
|
When traits are combined with @racket[trait-sum], the combination
|
|
drops @racket[inherit], @racket[inherit/super],
|
|
@racket[inherit/inner], and @racket[inherit-field] declarations when a
|
|
definition is supplied for the same method or field name by another
|
|
trait. The @racket[trait-sum] operation fails (the
|
|
@exnraise[exn:fail:contract]) if any of the traits to combine define a
|
|
method or field with the same name, or if an @racket[inherit/super] or
|
|
@racket[inherit/inner] declaration to be dropped is inconsistent with
|
|
the supplied definition. In other words, declaring a method with
|
|
@racket[inherit], @racket[inherit/super], or @racket[inherit/inner],
|
|
does not count as defining the method; at the same time, for example,
|
|
a trait that contains an @racket[inherit/super] declaration for a
|
|
method @racket[m] cannot be combined with a trait that defines
|
|
@racket[m] as @racket[augment], since no class could satisfy the
|
|
requirements of both @racket[augment] and @racket[inherit/super] when
|
|
the trait is later converted to a mixin and applied to a class.}
|
|
|
|
|
|
@defform[(trait-exclude trait-expr id)]{
|
|
|
|
Produces a new @tech{trait} that is like the @tech{trait} result of
|
|
@racket[trait-expr], but with the definition of a method named by
|
|
@racket[id] removed; as the method definition is removed, either an
|
|
@racket[inherit], @racket[inherit/super], or @racket[inherit/inner]
|
|
declaration is added:
|
|
|
|
@itemize[
|
|
|
|
@item{A method declared with @racket[public], @racket[pubment], or
|
|
@racket[public-final] is replaced with an @racket[inherit]
|
|
declaration.}
|
|
|
|
@item{A method declared with @racket[override] or @racket[override-final]
|
|
is replaced with an @racket[inherit/super] declaration.}
|
|
|
|
@item{A method declared with @racket[augment], @racket[augride], or
|
|
@racket[augment-final] is replaced with an @racket[inherit/inner] declaration.}
|
|
|
|
@item{A method declared with @racket[overment] is not replaced
|
|
with any @racket[inherit] declaration.}
|
|
|
|
]
|
|
|
|
If the trait produced by @racket[trait-expr] has no method definition for
|
|
@racket[id], the @exnraise[exn:fail:contract].}
|
|
|
|
|
|
@defform[(trait-exclude-field trait-expr id)]{
|
|
|
|
Produces a new @tech{trait} that is like the @tech{trait} result of
|
|
@racket[trait-expr], but with the definition of a field named by
|
|
@racket[id] removed; as the field definition is removed, an
|
|
@racket[inherit-field] declaration is added.}
|
|
|
|
|
|
@defform[(trait-alias trait-expr id new-id)]{
|
|
|
|
Produces a new @tech{trait} that is like the @tech{trait} result of
|
|
@racket[trait-expr], but the definition and declaration of the method
|
|
named by @racket[id] is duplicated with the name @racket[new-id]. The
|
|
consistency requirements for the resulting trait are the same as for
|
|
@racket[trait-sum], otherwise the @exnraise[exn:fail:contract]. This
|
|
operation does not rename any other use of @racket[id], such as in
|
|
method calls (even method calls to @racket[identifier] in the cloned
|
|
definition for @racket[new-id]).}
|
|
|
|
|
|
@defform[(trait-rename trait-expr id new-id)]{
|
|
|
|
Produces a new @tech{trait} that is like the @tech{trait} result of
|
|
@racket[trait-expr], but all definitions and references to methods
|
|
named @racket[id] are replaced by definitions and references to
|
|
methods named by @racket[new-id]. The consistency requirements for the
|
|
resulting trait are the same as for @racket[trait-sum], otherwise the
|
|
@exnraise[exn:fail:contract].}
|
|
|
|
|
|
@defform[(trait-rename-field trait-expr id new-id)]{
|
|
|
|
Produces a new @tech{trait} that is like the @tech{trait} result of
|
|
@racket[trait-expr], but all definitions and references to fields
|
|
named @racket[id] are replaced by definitions and references to fields
|
|
named by @racket[new-id]. The consistency requirements for the
|
|
resulting trait are the same as for @racket[trait-sum], otherwise the
|
|
@exnraise[exn:fail:contract].}
|
|
|
|
@; ------------------------------------------------------------------------
|
|
|
|
@section{Object and Class Contracts}
|
|
|
|
@defform/subs[
|
|
#:literals (field init init-field inherit inherit-field super inner override augment augride absent)
|
|
|
|
(class/c maybe-opaque member-spec ...)
|
|
|
|
([maybe-opaque
|
|
(code:line)
|
|
(code:line #:opaque)
|
|
(code:line #:opaque #:ignore-local-member-names)]
|
|
|
|
[member-spec
|
|
method-spec
|
|
(field field-spec ...)
|
|
(init field-spec ...)
|
|
(init-field field-spec ...)
|
|
(inherit method-spec ...)
|
|
(inherit-field field-spec ...)
|
|
(super method-spec ...)
|
|
(inner method-spec ...)
|
|
(override method-spec ...)
|
|
(augment method-spec ...)
|
|
(augride method-spec ...)
|
|
(absent absent-spec ...)]
|
|
|
|
[method-spec
|
|
method-id
|
|
(method-id method-contract-expr)]
|
|
[field-spec
|
|
field-id
|
|
(field-id contract-expr)]
|
|
[absent-spec
|
|
method-id
|
|
(field field-id ...)])]{
|
|
Produces a contract for a class.
|
|
|
|
There are two major categories of contracts listed in a @racket[class/c]
|
|
form: external and internal contracts. External contracts govern behavior
|
|
when an object is instantiated from a class or when methods or fields are
|
|
accessed via an object of that class. Internal contracts govern behavior
|
|
when method or fields are accessed within the class hierarchy. This
|
|
separation allows for stronger contracts for class clients and weaker
|
|
contracts for subclasses.
|
|
|
|
Method contracts must contain an additional initial argument which corresponds
|
|
to the implicit @racket[this] parameter of the method. This allows for
|
|
contracts which discuss the state of the object when the method is called
|
|
(or, for dependent contracts, in other parts of the contract). Alternative
|
|
contract forms, such as @racket[->m], are provided as a shorthand
|
|
for writing method contracts.
|
|
|
|
Methods and fields listed in an @racket[absent] clause must @emph{not} be present in the class.
|
|
|
|
A class contract can be specified to be @emph{opaque} with the @racket[#:opaque]
|
|
keyword. An opaque class contract will only accept a class that defines
|
|
exactly the external methods and fields specified by the contract. A contract error
|
|
is raised if the contracted class contains any methods or fields that are
|
|
not specified. Methods or fields with local member names (i.e., defined with
|
|
@racket[define-local-member-name]) are ignored for this check if
|
|
@racket[#:ignore-local-member-names] is provided.
|
|
|
|
The external contracts are as follows:
|
|
|
|
@itemize[
|
|
@item{An external method contract without a tag describes the behavior
|
|
of the implementation of @racket[method-id] on method sends to an
|
|
object of the contracted class. This contract will continue to be
|
|
checked in subclasses until the contracted class's implementation is
|
|
no longer the entry point for dynamic dispatch.
|
|
|
|
If only the field name is present, this is equivalent to insisting only
|
|
that the method is present in the class.
|
|
|
|
@examples[#:eval class-eval
|
|
(eval:no-prompt
|
|
(define woody%
|
|
(class object%
|
|
(define/public (draw who)
|
|
(format "reach for the sky, ~a" who))
|
|
(super-new)))
|
|
|
|
(define/contract woody+c%
|
|
(class/c [draw (->m symbol? string?)])
|
|
woody%))
|
|
|
|
(send (new woody%) draw #f)
|
|
(send (new woody+c%) draw 'zurg)
|
|
(eval:error (send (new woody+c%) draw #f))]
|
|
}
|
|
@item{An external field contract, tagged with @racket[field], describes the
|
|
behavior of the value contained in that field when accessed from outside
|
|
the class. Since fields may be mutated, these contracts
|
|
are checked on any external access (via @racket[get-field])
|
|
and external mutations (via @racket[set-field!]) of the field.
|
|
|
|
If only the field name is present, this is equivalent to using the
|
|
contract @racket[any/c] (but it is checked more efficiently).
|
|
|
|
@examples[#:eval class-eval
|
|
(eval:no-prompt
|
|
(define woody/hat%
|
|
(class woody%
|
|
(field [hat-location 'uninitialized])
|
|
(define/public (lose-hat) (set! hat-location 'lost))
|
|
(define/public (find-hat) (set! hat-location 'on-head))
|
|
(super-new)))
|
|
(define/contract woody/hat+c%
|
|
(class/c [draw (->m symbol? string?)]
|
|
[lose-hat (->m void?)]
|
|
[find-hat (->m void?)]
|
|
(field [hat-location (or/c 'on-head 'lost)]))
|
|
woody/hat%))
|
|
|
|
(get-field hat-location (new woody/hat%))
|
|
(let ([woody (new woody/hat+c%)])
|
|
(send woody lose-hat)
|
|
(get-field hat-location woody))
|
|
(eval:error (get-field hat-location (new woody/hat+c%)))
|
|
(eval:error
|
|
(let ([woody (new woody/hat+c%)])
|
|
(set-field! hat-location woody 'under-the-dresser)))]
|
|
|
|
}
|
|
@item{An initialization argument contract, tagged with @racket[init],
|
|
describes the expected behavior of the value paired with that name
|
|
during class instantiation. The same name can be provided more than
|
|
once, in which case the first such contract in the @racket[class/c]
|
|
form is applied to the first value tagged with that name in the list
|
|
of initialization arguments, and so on.
|
|
|
|
If only the initialization argument name is present, this is equivalent to using the
|
|
contract @racket[any/c] (but it is checked more efficiently).
|
|
|
|
@examples[#:eval class-eval
|
|
(eval:no-prompt
|
|
(define woody/init-hat%
|
|
(class woody%
|
|
(init init-hat-location)
|
|
(field [hat-location init-hat-location])
|
|
(define/public (lose-hat) (set! hat-location 'lost))
|
|
(define/public (find-hat) (set! hat-location 'on-head))
|
|
(super-new)))
|
|
(define/contract woody/init-hat+c%
|
|
(class/c [draw (->m symbol? string?)]
|
|
[lose-hat (->m void?)]
|
|
[find-hat (->m void?)]
|
|
(init [init-hat-location (or/c 'on-head 'lost)])
|
|
(field [hat-location (or/c 'on-head 'lost)]))
|
|
woody/init-hat%))
|
|
(get-field hat-location
|
|
(new woody/init-hat+c%
|
|
[init-hat-location 'lost]))
|
|
(eval:error
|
|
(get-field hat-location
|
|
(new woody/init-hat+c%
|
|
[init-hat-location 'slinkys-mouth])))]
|
|
|
|
}
|
|
@item{The contracts listed in an @racket[init-field] section are
|
|
treated as if each contract appeared in an @racket[init] section and
|
|
a @racket[field] section.}
|
|
]
|
|
|
|
The internal contracts restrict the behavior of method calls
|
|
made between classes and their subclasses; such calls are not
|
|
controlled by the class contracts described above.
|
|
|
|
As with the external contracts, when a method or field name is specified
|
|
but no contract appears, the contract is satisfied merely with the
|
|
presence of the corresponding field or method.
|
|
|
|
@itemize[
|
|
@item{A method contract tagged with @racket[inherit] describes the
|
|
behavior of the method when invoked directly (i.e., via
|
|
@racket[inherit]) in any subclass of the contracted class. This
|
|
contract, like external method contracts, applies until the
|
|
contracted class's method implementation is no longer the entry point
|
|
for dynamic dispatch.
|
|
|
|
@examples[#:eval class-eval
|
|
(new (class woody+c%
|
|
(inherit draw)
|
|
(super-new)
|
|
(printf "woody sez: “~a”\n" (draw "evil dr porkchop"))))
|
|
(eval:no-prompt
|
|
(define/contract woody+c-inherit%
|
|
(class/c (inherit [draw (->m symbol? string?)]))
|
|
woody+c%))
|
|
(eval:error
|
|
(new (class woody+c-inherit%
|
|
(inherit draw)
|
|
(printf "woody sez: ~a\n" (draw "evil dr porkchop")))))]
|
|
|
|
}
|
|
@item{A method contract tagged with @racket[super] describes the behavior of
|
|
@racket[method-id] when called by the @racket[super] form in a
|
|
subclass. This contract only affects @racket[super] calls in
|
|
subclasses which call the contract class's implementation of
|
|
@racket[method-id].
|
|
|
|
This example shows how to extend the @racket[draw] method
|
|
so that if it is passed two arguments, it combines two
|
|
calls to the original @racket[draw] method, but with a
|
|
contract the controls how the @racket[super] methods must
|
|
be invoked.
|
|
|
|
@examples[#:eval class-eval
|
|
(eval:no-prompt
|
|
(define/contract woody2+c%
|
|
(class/c (super [draw (->m symbol? string?)]))
|
|
(class woody%
|
|
(define/override draw
|
|
(case-lambda
|
|
[(a) (super draw a)]
|
|
[(a b) (string-append (super draw a)
|
|
" and "
|
|
(super draw b))]))
|
|
(super-new))))
|
|
(send (new woody2+c%) draw 'evil-dr-porkchop 'zurg)
|
|
(send (new woody2+c%) draw "evil dr porkchop" "zurg")]
|
|
|
|
The last call signals an error blaming @racket[woody2%] because
|
|
there is no contract checking the initial @racket[draw] call.
|
|
}
|
|
@item{A method contract tagged with @racket[inner] describes the
|
|
behavior the class expects of an augmenting method in a subclass.
|
|
This contract affects any implementations of @racket[method-id] in
|
|
subclasses which can be called via @racket[inner] from the contracted
|
|
class. This means a subclass which implements @racket[method-id] via
|
|
@racket[augment] or @racket[overment] stop future subclasses from
|
|
being affected by the contract, since further extension cannot be
|
|
reached via the contracted class.}
|
|
@item{A method contract tagged with @racket[override] describes the
|
|
behavior expected by the contracted class for @racket[method-id] when
|
|
called directly (i.e. by the application @racket[(method-id ...)]).
|
|
This form can only be used if overriding the method in subclasses
|
|
will change the entry point to the dynamic dispatch chain (i.e., the
|
|
method has never been augmentable).
|
|
|
|
This time, instead of overriding @racket[draw] to support
|
|
two arguments, we can make a new method, @racket[draw2] that
|
|
takes the two arguments and calls @racket[draw]. We also
|
|
add a contract to make sure that overriding @racket[draw]
|
|
doesn't break @racket[draw2].
|
|
|
|
@examples[#:eval class-eval
|
|
(eval:no-prompt
|
|
(define/contract woody2+override/c%
|
|
(class/c (override [draw (->m symbol? string?)]))
|
|
(class woody+c%
|
|
(inherit draw)
|
|
(define/public (draw2 a b)
|
|
(string-append (draw a)
|
|
" and "
|
|
(draw b)))
|
|
(super-new)))
|
|
|
|
(define woody2+broken-draw
|
|
(class woody2+override/c%
|
|
(define/override (draw x)
|
|
'not-a-string)
|
|
(super-new))))
|
|
|
|
(eval:error
|
|
(send (new woody2+broken-draw) draw2
|
|
'evil-dr-porkchop
|
|
'zurg))]
|
|
|
|
|
|
}
|
|
@item{A method contract tagged with either @racket[augment] or
|
|
@racket[augride] describes the behavior provided by the contracted
|
|
class for @racket[method-id] when called directly from subclasses.
|
|
These forms can only be used if the method has previously been
|
|
augmentable, which means that no augmenting or overriding
|
|
implementation will change the entry point to the dynamic dispatch
|
|
chain. @racket[augment] is used when subclasses can augment the
|
|
method, and @racket[augride] is used when subclasses can override the
|
|
current augmentation.}
|
|
@item{A field contract tagged with @racket[inherit-field] describes
|
|
the behavior of the value contained in that field when accessed
|
|
directly (i.e., via @racket[inherit-field]) in any subclass of the
|
|
contracted class. Since fields may be mutated, these contracts are
|
|
checked on any access and/or mutation of the field that occurs in
|
|
such subclasses.}
|
|
|
|
@history[#:changed "6.1.1.8"
|
|
@string-append{Opaque class/c now optionally ignores local
|
|
member names if an additional keyword is supplied.}]
|
|
]}
|
|
|
|
@defform[(absent absent-spec ...)]{
|
|
See @racket[class/c]; use outside of a @racket[class/c] form is a syntax error.
|
|
}
|
|
|
|
@defform[(->m dom ... range)]{
|
|
Similar to @racket[->], except that the domain of the resulting contract
|
|
contains one more element than the stated domain, where the first
|
|
(implicit) argument is contracted with @racket[any/c]. This contract is
|
|
useful for writing simpler method contracts when no properties of
|
|
@racket[this] need to be checked.}
|
|
|
|
@defform[(->*m (mandatory-dom ...) (optional-dom ...) rest range)]{
|
|
Similar to @racket[->*], except that the mandatory domain of the
|
|
resulting contract contains one more element than the stated domain,
|
|
where the first (implicit) argument is contracted with
|
|
@racket[any/c]. This contract is useful for writing simpler method
|
|
contracts when no properties of @racket[this] need to be checked.}
|
|
|
|
@defform[(case->m (-> dom ... rest range) ...)]{
|
|
Similar to @racket[case->], except that the mandatory domain of each
|
|
case of the resulting contract contains one more element than the stated
|
|
domain, where the first (implicit) argument is contracted with
|
|
@racket[any/c]. This contract is useful for writing simpler method
|
|
contracts when no properties of @racket[this] need to be checked.}
|
|
|
|
@defform[(->dm (mandatory-dependent-dom ...)
|
|
(optional-dependent-dom ...)
|
|
dependent-rest
|
|
pre-cond
|
|
dep-range)]{
|
|
Similar to @racket[->d], except that the mandatory domain of the resulting contract
|
|
contains one more element than the stated domain, where the first (implicit) argument is contracted
|
|
with @racket[any/c]. In addition, @racket[this] is appropriately bound in the body of the contract.
|
|
This contract is useful for writing simpler method contracts when no properties
|
|
of @racket[this] need to be checked.}
|
|
|
|
@defform/subs[
|
|
#:literals (field)
|
|
|
|
(object/c member-spec ...)
|
|
|
|
([member-spec
|
|
method-spec
|
|
(field field-spec ...)]
|
|
|
|
[method-spec
|
|
method-id
|
|
(method-id method-contract)]
|
|
[field-spec
|
|
field-id
|
|
(field-id contract-expr)])]{
|
|
Produces a contract for an object.
|
|
|
|
Unlike the older form @racket[object-contract], but like
|
|
@racket[class/c], arbitrary contract expressions are allowed.
|
|
Also, method contracts for @racket[object/c] follow those for
|
|
@racket[class/c]. An object wrapped with @racket[object/c]
|
|
behaves as if its class had been wrapped with the equivalent
|
|
@racket[class/c] contract.
|
|
}
|
|
|
|
@defproc[(instanceof/c [class-contract contract?]) contract?]{
|
|
Produces a contract for an object, where the object is an
|
|
instance of a class that conforms to @racket[class-contract].
|
|
}
|
|
|
|
@defproc[(dynamic-object/c [method-names (listof symbol?)]
|
|
[method-contracts (listof contract?)]
|
|
[field-names (listof symbol?)]
|
|
[field-contracts (listof contract?)])
|
|
contract?]{
|
|
Produces a contract for an object, similar to @racket[object/c] but
|
|
where the names and contracts for both methods and fields can be
|
|
computed dynamically. The list of names and contracts for both
|
|
methods and field respectively must have the same lengths.
|
|
}
|
|
|
|
@defform/subs[
|
|
#:literals (field -> ->* ->d)
|
|
|
|
(object-contract member-spec ...)
|
|
|
|
([member-spec
|
|
(method-id method-contract)
|
|
(field field-id contract-expr)]
|
|
|
|
[method-contract
|
|
(-> dom ... range)
|
|
(->* (mandatory-dom ...)
|
|
(optional-dom ...)
|
|
rest
|
|
range)
|
|
(->d (mandatory-dependent-dom ...)
|
|
(optional-dependent-dom ...)
|
|
dependent-rest
|
|
pre-cond
|
|
dep-range)]
|
|
|
|
[dom dom-expr (code:line keyword dom-expr)]
|
|
[range range-expr (values range-expr ...) any]
|
|
[mandatory-dom dom-expr (code:line keyword dom-expr)]
|
|
[optional-dom dom-expr (code:line keyword dom-expr)]
|
|
[rest (code:line) (code:line #:rest rest-expr)]
|
|
[mandatory-dependent-dom [id dom-expr] (code:line keyword [id dom-expr])]
|
|
[optional-dependent-dom [id dom-expr] (code:line keyword [id dom-expr])]
|
|
[dependent-rest (code:line) (code:line #:rest id rest-expr)]
|
|
[pre-cond (code:line) (code:line #:pre-cond boolean-expr)]
|
|
[dep-range any
|
|
(code:line [id range-expr] post-cond)
|
|
(code:line (values [id range-expr] ...) post-cond)]
|
|
[post-cond (code:line) (code:line #:post-cond boolean-expr)]
|
|
)]{
|
|
|
|
Produces a contract for an object.
|
|
|
|
Each of the contracts for a method has the same semantics as
|
|
the corresponding function contract, but the syntax of the
|
|
method contract must be written directly in the body of the
|
|
object-contract---much like the way that methods in class
|
|
definitions use the same syntax as regular function
|
|
definitions, but cannot be arbitrary procedures. Unlike the
|
|
method contracts for @racket[class/c], the implicit @racket[this]
|
|
argument is not part of the contract. To allow for the use of
|
|
@racket[this] in dependent contracts, @racket[->d] contracts
|
|
implicitly bind @racket[this] to the object itself.}
|
|
|
|
|
|
@defthing[mixin-contract contract?]{
|
|
|
|
A @tech{function contract} that recognizes mixins. It guarantees that
|
|
the input to the function is a class and the result of the function is
|
|
a subclass of the input.}
|
|
|
|
@defproc[(make-mixin-contract [type (or/c class? interface?)] ...) contract?]{
|
|
|
|
Produces a @tech{function contract} that guarantees the input to the
|
|
function is a class that implements/subclasses each @racket[type], and
|
|
that the result of the function is a subclass of the input.}
|
|
|
|
@defproc[(is-a?/c [type (or/c class? interface?)]) flat-contract?]{
|
|
|
|
Accepts a class or interface and returns a flat contract that
|
|
recognizes objects that instantiate the class/interface.}
|
|
|
|
|
|
@defproc[(implementation?/c [interface interface?]) flat-contract?]{
|
|
|
|
Returns a flat contract that recognizes classes that implement
|
|
@racket[interface].}
|
|
|
|
|
|
@defproc[(subclass?/c [class class?]) flat-contract?]{
|
|
|
|
Returns a flat contract that recognizes classes that
|
|
are subclasses of @racket[class].}
|
|
|
|
|
|
@; ------------------------------------------------------------------------
|
|
|
|
@section[#:tag "objectequality"]{Object Equality and Hashing}
|
|
|
|
By default, objects that are instances of different classes or that
|
|
are instances of a non-transparent class are @racket[equal?] only if
|
|
they are @racket[eq?]. Like transparent structures, two objects that
|
|
are instances of the same transparent class (i.e., every superclass of
|
|
the class has @racket[#f] as its inspector) are @racket[equal?] when
|
|
their field values are @racket[equal?].
|
|
|
|
To customize the way that a class instance is compared to other
|
|
instances by @racket[equal?], implement the @racket[equal<%>]
|
|
interface.
|
|
|
|
@definterface[equal<%> ()]{
|
|
|
|
The @racket[equal<%>] interface includes three methods, which are
|
|
analogous to the functions provided for a structure type with
|
|
@racket[prop:equal+hash]:
|
|
|
|
@itemize[
|
|
|
|
@item{@racket[equal-to?] --- Takes two arguments. The first argument
|
|
is an object that is an instance of the same class (or a subclass
|
|
that does not re-declare its implementation of @racket[equal<%>])
|
|
and that is being compared to the target object. The second argument
|
|
is an @racket[equal?]-like procedure of two arguments that should be
|
|
used for recursive equality testing. The result should be a true
|
|
value if the object and the first argument of the method are equal,
|
|
@racket[#f] otherwise.}
|
|
|
|
@item{@racket[equal-hash-code-of] --- Takes one argument, which is a
|
|
procedure of one argument that should be used for recursive hash-code
|
|
computation. The result should be an exact integer representing the
|
|
target object's hash code.}
|
|
|
|
@item{@racket[equal-secondary-hash-code-of] --- Takes one argument,
|
|
which is a procedure of one argument that should be used for
|
|
recursive hash-code computation. The result should be an exact
|
|
integer representing the target object's secondary hash code.}
|
|
|
|
]
|
|
|
|
The @racket[equal<%>] interface is unusual in that declaring the
|
|
implementation of the interface is different from inheriting the
|
|
interface. Two objects can be equal only if they are instances of
|
|
classes whose most specific ancestor to explicitly implement
|
|
@racket[equal<%>] is the same ancestor.
|
|
|
|
See @racket[prop:equal+hash] for more information on equality
|
|
comparisons and hash codes. The @racket[equal<%>] interface is
|
|
implemented with @racket[interface*] and @racket[prop:equal+hash].}
|
|
|
|
Example:
|
|
@codeblock|{
|
|
#lang racket
|
|
|
|
;; Case insensitive words:
|
|
(define ci-word%
|
|
(class* object% (equal<%>)
|
|
|
|
;; Initialization
|
|
(init-field word)
|
|
(super-new)
|
|
|
|
;; We define equality to ignore case:
|
|
(define/public (equal-to? other recur)
|
|
(string-ci=? word (get-field word other)))
|
|
|
|
;; The hash codes need to be insensitive to casing as well.
|
|
;; We'll just downcase the word and get its hash code.
|
|
(define/public (equal-hash-code-of hash-code)
|
|
(hash-code (string-downcase word)))
|
|
|
|
(define/public (equal-secondary-hash-code-of hash-code)
|
|
(hash-code (string-downcase word)))))
|
|
|
|
;; We can create a hash with a single word:
|
|
(define h (make-hash))
|
|
(hash-set! h (new ci-word% [word "inconceivable!"]) 'value)
|
|
|
|
;; Lookup into the hash should be case-insensitive, so that
|
|
;; both of these should return 'value.
|
|
(hash-ref h (new ci-word% [word "inconceivable!"]))
|
|
(hash-ref h (new ci-word% [word "INCONCEIVABLE!"]))
|
|
|
|
;; Comparison fails if we use a non-ci-word%:
|
|
(hash-ref h "inconceivable!" 'i-dont-think-it-means-what-you-think-it-means)
|
|
}|
|
|
|
|
@; ------------------------------------------------------------------------
|
|
|
|
@section[#:tag "objectserialize"]{Object Serialization}
|
|
|
|
@defform[
|
|
(define-serializable-class* class-id superclass-expr
|
|
(interface-expr ...)
|
|
class-clause ...)
|
|
]{
|
|
|
|
Binds @racket[class-id] to a class, where @racket[superclass-expr],
|
|
the @racket[interface-expr]s, and the @racket[class-clause]s are as in
|
|
@racket[class*].
|
|
|
|
This form can only be used at the top level, either within a module
|
|
or outside. The @racket[class-id] identifier is bound to the new
|
|
class, and @racketidfont{deserialize-info:}@racket[class-id] is also
|
|
defined; if the definition is within a module, then the latter is
|
|
provided from a @racket[deserialize-info] submodule via @racket[module+].
|
|
|
|
Serialization for the class works in one of two ways:
|
|
|
|
@itemize[
|
|
|
|
@item{If the class implements the built-in interface
|
|
@racket[externalizable<%>], then an object is serialized by
|
|
calling its @racket[externalize] method; the result can be
|
|
anything that is serializable (but, obviously, should not be
|
|
the object itself). Deserialization creates an instance of the
|
|
class with no initialization arguments, and then calls the
|
|
object's @racket[internalize] method with the result of
|
|
@racket[externalize] (or, more precisely, a deserialized
|
|
version of the serialized result of a previous call).
|
|
|
|
To support this form of serialization, the class must be
|
|
instantiable with no initialization arguments. Furthermore,
|
|
cycles involving only instances of the class (and other such
|
|
classes) cannot be serialized.}
|
|
|
|
@item{If the class does not implement @racket[externalizable<%>],
|
|
then every superclass of the class must be either serializable
|
|
or transparent (i.e,. have @racket[#f] as its
|
|
inspector). Serialization and deserialization are fully
|
|
automatic, and may involve cycles of instances.
|
|
|
|
To support cycles of instances, deserialization may create an
|
|
instance of the call with all fields as the undefined value,
|
|
and then mutate the object to set the field
|
|
values. Serialization support does not otherwise make an
|
|
object's fields mutable.}
|
|
|
|
]
|
|
|
|
In the second case, a serializable subclass can implement
|
|
@racket[externalizable<%>], in which case the @racket[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
|
|
@racket[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 @racket[externalize] method are ignored
|
|
for instances of non-serializable subclasses.}
|
|
|
|
|
|
@defform[
|
|
(define-serializable-class class-id superclass-expr
|
|
class-clause ...)
|
|
]{
|
|
|
|
Like @racket[define-serializable-class*], but without interface
|
|
expressions (analogous to @racket[class]).}
|
|
|
|
|
|
@definterface[externalizable<%> ()]{
|
|
|
|
The @racket[externalizable<%>] interface includes only the
|
|
@racket[externalize] and @racket[internalize] methods. See
|
|
@racket[define-serializable-class*] for more information.}
|
|
|
|
@; ------------------------------------------------------------------------
|
|
|
|
@section[#:tag "objectprinting"]{Object Printing}
|
|
|
|
To customize the way that a class instance is printed by
|
|
@racket[print], @racket[write] and @racket[display], implement the
|
|
@racket[printable<%>] interface.
|
|
|
|
@defthing[printable<%> interface?]{
|
|
|
|
The @racket[printable<%>] interface includes only the
|
|
@racket[custom-print], @racket[custom-write], and
|
|
@racket[custom-display] methods. The @racket[custom-print] method
|
|
accepts two arguments: the destination port and the current
|
|
@racket[quasiquote] depth as an exact nonnegative integer. The
|
|
@racket[custom-write] and @racket[custom-display] methods each accepts
|
|
a single argument, which is the destination port to @racket[write] or
|
|
@racket[display] the object.
|
|
|
|
Calls to the @racket[custom-print], @racket[custom-write], or
|
|
@racket[custom-display] methods are like calls to a procedure attached
|
|
to a structure type through the @racket[prop:custom-write]
|
|
property. In particular, recursive printing can trigger an escape from
|
|
the call.
|
|
|
|
See @racket[prop:custom-write] for more information. The
|
|
@racket[printable<%>] interface is implemented with
|
|
@racket[interface*] and @racket[prop:custom-write].}
|
|
|
|
@defthing[writable<%> interface?]{
|
|
|
|
Like @racket[printable<%>], but includes only the
|
|
@racket[custom-write] and @racket[custom-display] methods.
|
|
A @racket[print] request is directed to @racket[custom-write].}
|
|
|
|
@; ------------------------------------------------------------------------
|
|
|
|
@section[#:tag "objectutils"]{Object, Class, and Interface Utilities}
|
|
|
|
@defproc[(object? [v any/c]) boolean?]{
|
|
|
|
Returns @racket[#t] if @racket[v] is an object, @racket[#f] otherwise.
|
|
|
|
@examples[#:eval class-eval
|
|
(object? (new object%))
|
|
(object? object%)
|
|
(object? "clam chowder")
|
|
]}
|
|
|
|
|
|
@defproc[(class? [v any/c]) boolean?]{
|
|
|
|
Returns @racket[#t] if @racket[v] is a class, @racket[#f] otherwise.
|
|
|
|
@examples[#:eval class-eval
|
|
(class? object%)
|
|
(class? (class object% (super-new)))
|
|
(class? (new object%))
|
|
(class? "corn chowder")
|
|
]}
|
|
|
|
|
|
@defproc[(interface? [v any/c]) boolean?]{
|
|
|
|
Returns @racket[#t] if @racket[v] is an interface, @racket[#f] otherwise.
|
|
|
|
@examples[#:eval class-eval
|
|
(interface? (interface () empty cons first rest))
|
|
(interface? object%)
|
|
(interface? "gazpacho")
|
|
]}
|
|
|
|
|
|
@defproc[(generic? [v any/c]) boolean?]{
|
|
|
|
Returns @racket[#t] if @racket[v] is a @tech{generic}, @racket[#f] otherwise.
|
|
|
|
@examples[#:eval class-eval
|
|
(define c%
|
|
(class object%
|
|
(super-new)
|
|
(define/public (m x)
|
|
(+ 3.14 x))))
|
|
|
|
(generic? (generic c% m))
|
|
(generic? c%)
|
|
(generic? "borscht")
|
|
]}
|
|
|
|
|
|
@defproc[(object=? [a object?] [b object?]) boolean?]{
|
|
|
|
Determines whether @racket[a] and @racket[b] were returned from
|
|
the same call to @racket[new] or not. If the two objects
|
|
have fields, this procedure determines whether mutating a field
|
|
of one would change that field in the other.
|
|
|
|
This procedure is similar in spirit to
|
|
@racket[eq?] but also works properly with contracts
|
|
(and has a stronger guarantee).
|
|
|
|
@examples[#:eval class-ctc-eval
|
|
(define obj-1 (new object%))
|
|
(define obj-2 (new object%))
|
|
(define/contract obj-3 (object/c) obj-1)
|
|
|
|
(object=? obj-1 obj-1)
|
|
(object=? obj-1 obj-2)
|
|
(object=? obj-1 obj-3)
|
|
|
|
(eq? obj-1 obj-1)
|
|
(eq? obj-1 obj-2)
|
|
(eq? obj-1 obj-3)
|
|
]}
|
|
|
|
|
|
@defproc[(object-or-false=? [a (or/c object? #f)] [b (or/c object? #f)]) boolean?]{
|
|
|
|
Like @racket[object=?], but accepts @racket[#f] for either argument and
|
|
returns @racket[#t] if both arguments are @racket[#f].
|
|
|
|
@examples[#:eval class-ctc-eval
|
|
(object-or-false=? #f (new object%))
|
|
(object-or-false=? (new object%) #f)
|
|
(object-or-false=? #f #f)
|
|
]
|
|
|
|
@history[#:added "6.1.1.8"]}
|
|
|
|
|
|
@defproc[(object->vector [object object?] [opaque-v any/c #f]) vector?]{
|
|
|
|
Returns a vector representing @racket[object] that shows its
|
|
inspectable fields, analogous to @racket[struct->vector].
|
|
|
|
@examples[#:eval class-eval
|
|
(object->vector (new object%))
|
|
(object->vector (new (class object%
|
|
(super-new)
|
|
(field [x 5] [y 10]))))
|
|
]}
|
|
|
|
|
|
@defproc[(class->interface [class class?]) interface?]{
|
|
|
|
Returns the interface implicitly defined by @racket[class].
|
|
|
|
@examples[#:eval class-eval
|
|
(class->interface object%)
|
|
]}
|
|
|
|
|
|
@defproc[(object-interface [object object?]) interface?]{
|
|
|
|
Returns the interface implicitly defined by the class of
|
|
@racket[object].
|
|
|
|
@examples[#:eval class-eval
|
|
(object-interface (new object%))
|
|
]}
|
|
|
|
|
|
@defproc[(is-a? [v any/c] [type (or/c interface? class?)]) boolean?]{
|
|
|
|
Returns @racket[#t] if @racket[v] is an instance of a class
|
|
@racket[type] or a class that implements an interface @racket[type],
|
|
@racket[#f] otherwise.
|
|
|
|
@examples[#:eval class-eval
|
|
(define point<%> (interface () get-x get-y))
|
|
(define 2d-point%
|
|
(class* object% (point<%>)
|
|
(super-new)
|
|
(field [x 0] [y 0])
|
|
(define/public (get-x) x)
|
|
(define/public (get-y) y)))
|
|
|
|
(is-a? (new 2d-point%) 2d-point%)
|
|
(is-a? (new 2d-point%) point<%>)
|
|
(is-a? (new object%) 2d-point%)
|
|
(is-a? (new object%) point<%>)
|
|
]}
|
|
|
|
|
|
@defproc[(subclass? [v any/c] [cls class?]) boolean?]{
|
|
|
|
Returns @racket[#t] if @racket[v] is a class derived from (or equal
|
|
to) @racket[cls], @racket[#f] otherwise.
|
|
|
|
@examples[#:eval class-eval
|
|
(subclass? (class object% (super-new)) object%)
|
|
(subclass? object% (class object% (super-new)))
|
|
(subclass? object% object%)
|
|
]}
|
|
|
|
|
|
@defproc[(implementation? [v any/c] [intf interface?]) boolean?]{
|
|
|
|
Returns @racket[#t] if @racket[v] is a class that implements
|
|
@racket[intf], @racket[#f] otherwise.
|
|
|
|
@examples[#:eval class-eval
|
|
(define i<%> (interface () go))
|
|
(define c%
|
|
(class* object% (i<%>)
|
|
(super-new)
|
|
(define/public (go) 'go)))
|
|
|
|
(implementation? c% i<%>)
|
|
(implementation? object% i<%>)
|
|
]}
|
|
|
|
|
|
@defproc[(interface-extension? [v any/c] [intf interface?]) boolean?]{
|
|
|
|
Returns @racket[#t] if @racket[v] is an interface that extends
|
|
@racket[intf], @racket[#f] otherwise.
|
|
|
|
@examples[#:eval class-eval
|
|
(define point<%> (interface () get-x get-y))
|
|
(define colored-point<%> (interface (point<%>) color))
|
|
|
|
(interface-extension? colored-point<%> point<%>)
|
|
(interface-extension? point<%> colored-point<%>)
|
|
(interface-extension? (interface () get-x get-y get-z) point<%>)
|
|
]}
|
|
|
|
|
|
@defproc[(method-in-interface? [sym symbol?] [intf interface?]) boolean?]{
|
|
|
|
Returns @racket[#t] if @racket[intf] (or any of its ancestor
|
|
interfaces) includes a member with the name @racket[sym], @racket[#f]
|
|
otherwise.
|
|
|
|
@examples[#:eval class-eval
|
|
(define i<%> (interface () get-x get-y))
|
|
(method-in-interface? 'get-x i<%>)
|
|
(method-in-interface? 'get-z i<%>)
|
|
]}
|
|
|
|
|
|
@defproc[(interface->method-names [intf interface?]) (listof symbol?)]{
|
|
|
|
Returns a list of symbols for the method names in @racket[intf],
|
|
including methods inherited from superinterfaces, but not including
|
|
methods whose names are local (i.e., declared with
|
|
@racket[define-local-member-name]).
|
|
|
|
@examples[#:eval class-eval
|
|
(define i<%> (interface () get-x get-y))
|
|
(interface->method-names i<%>)
|
|
]}
|
|
|
|
|
|
@defproc[(object-method-arity-includes? [object object?] [sym symbol?] [cnt exact-nonnegative-integer?])
|
|
boolean?]{
|
|
|
|
Returns @racket[#t] if @racket[object] has a method named @racket[sym]
|
|
that accepts @racket[cnt] arguments, @racket[#f] otherwise.
|
|
|
|
@examples[#:eval class-eval
|
|
(define c%
|
|
(class object%
|
|
(super-new)
|
|
(define/public (m x [y 0])
|
|
(+ x y))))
|
|
|
|
(object-method-arity-includes? (new c%) 'm 1)
|
|
(object-method-arity-includes? (new c%) 'm 2)
|
|
(object-method-arity-includes? (new c%) 'm 3)
|
|
(object-method-arity-includes? (new c%) 'n 1)
|
|
]}
|
|
|
|
|
|
@defproc[(field-names [object object?]) (listof symbol?)]{
|
|
|
|
Returns a list of all of the names of the fields bound in
|
|
@racket[object], including fields inherited from superinterfaces, but
|
|
not including fields whose names are local (i.e., declared with
|
|
@racket[define-local-member-name]).
|
|
|
|
@examples[#:eval class-eval
|
|
(field-names (new object%))
|
|
(field-names (new (class object% (super-new) (field [x 0] [y 0]))))
|
|
]}
|
|
|
|
|
|
@defproc[(object-info [object object?]) (values (or/c class? #f) boolean?)]{
|
|
|
|
Returns two values, analogous to the return
|
|
values of @racket[struct-info]:
|
|
@itemize[
|
|
|
|
@item{@racket[_class]: a class or @racket[#f]; the result is
|
|
@racket[#f] if the current inspector does not control any class for
|
|
which the @racket[object] is an instance.}
|
|
|
|
@item{@racket[_skipped?]: @racket[#f] if the first result corresponds
|
|
to the most specific class of @racket[object], @racket[#t]
|
|
otherwise.}
|
|
|
|
]}
|
|
|
|
|
|
@defproc[(class-info [class class?])
|
|
(values symbol?
|
|
exact-nonnegative-integer?
|
|
(listof symbol?)
|
|
(any/c exact-nonnegative-integer? . -> . any/c)
|
|
(any/c exact-nonnegative-integer? any/c . -> . any/c)
|
|
(or/c class? #f)
|
|
boolean?)]{
|
|
|
|
Returns seven values, analogous to the return
|
|
values of @racket[struct-type-info]:
|
|
|
|
@itemize[
|
|
|
|
@item{@racket[_name]: the class's name as a symbol;}
|
|
|
|
@item{@racket[_field-cnt]: the number of fields (public and private)
|
|
defined by the class;}
|
|
|
|
@item{@racket[_field-name-list]: a list of symbols corresponding to the
|
|
class's public fields; this list can be larger than @racket[_field-cnt]
|
|
because it includes inherited fields;}
|
|
|
|
@item{@racket[_field-accessor]: an accessor procedure for obtaining
|
|
field values in instances of the class; the accessor takes an
|
|
instance and a field index between @racket[0] (inclusive)
|
|
and @racket[_field-cnt] (exclusive);}
|
|
|
|
@item{@racket[_field-mutator]: a mutator procedure for modifying
|
|
field values in instances of the class; the mutator takes an
|
|
instance, a field index between @racket[0] (inclusive)
|
|
and @racket[_field-cnt] (exclusive), and a new field value;}
|
|
|
|
@item{@racket[_super-class]: a class for the most specific ancestor of
|
|
the given class that is controlled by the current inspector,
|
|
or @racket[#f] if no ancestor is controlled by the current
|
|
inspector;}
|
|
|
|
@item{@racket[_skipped?]: @racket[#f] if the sixth result is the most
|
|
specific ancestor class, @racket[#t] otherwise.}
|
|
|
|
]}
|
|
|
|
@defstruct[(exn:fail:object exn:fail) ()]{
|
|
|
|
Raised for @racket[class]-related failures, such as attempting to call
|
|
a method that is not supplied by an object.
|
|
|
|
}
|
|
|
|
@defproc[(class-seal [class class?]
|
|
[key symbol?]
|
|
[unsealed-inits (listof symbol?)]
|
|
[unsealed-fields (listof symbol?)]
|
|
[unsealed-methods (listof symbol?)]
|
|
[inst-proc (-> class? any)]
|
|
[member-proc (-> class? (listof symbol?) any)])
|
|
class?]{
|
|
|
|
Adds a seal to a given class keyed with the symbol @racket[key]. The
|
|
given @racket[unsealed-inits], @racket[unsealed-fields], and
|
|
@racket[unsealed-methods] list corresponding class members that are
|
|
unaffected by sealing.
|
|
|
|
When a class has any seals, the @racket[inst-proc] procedure is called
|
|
on instantiation (normally, this is used to raise an error on
|
|
instantiation) and the @racket[member-proc] function is called
|
|
(again, this is normally used to raise an error) when a subclass
|
|
attempts to add class members that are not listed in the unsealed lists.
|
|
|
|
The @racket[inst-proc] is called with the class value on which an
|
|
instantiation was attempted. The @racket[member-proc] is called with
|
|
the class value and the list of initialization argument, field, or
|
|
method names.
|
|
}
|
|
|
|
@defproc[(class-unseal [class class?]
|
|
[key symbol?]
|
|
[wrong-key-proc (-> class? any)])
|
|
class?]{
|
|
|
|
Removes a seal on a class that has been previously sealed with the
|
|
@racket[class-seal] function and the given @racket[key].
|
|
|
|
If the unseal removed all of the seals in the class, the class
|
|
value can be instantiated or subclassed freely. If the given
|
|
class value does not contain or any seals or does not contain
|
|
any seals with the given key, the @racket[wrong-key-proc] function
|
|
is called with the class value.
|
|
}
|
|
|
|
@; ----------------------------------------------------------------------
|
|
|
|
@include-section["surrogate.scrbl"]
|
|
|
|
@close-eval[class-eval]
|