racket/pkgs/racket-doc/scribblings/reference/define-struct.scrbl
Matthew Flatt 0523a5311c add structure-type sealing
The predicate for a seald structure type can be faster than a
predicate for a non-sealed structure type, and Chez Scheme takes
advantage of that opportunity.

The BC JIT could be improved to take advanatge of sealed structure
types, but it isn't.

This commit also fixes CS checking of a supertype for certain shapes
of prefab struct-type declarations.
2021-02-13 08:46:31 -07:00

423 lines
17 KiB
Racket

#lang scribble/doc
@(require "mz.rkt" (for-syntax racket/base) (for-label racket/serialize
racket/generic
racket/keyword-transform))
@(define posn-eval (make-base-eval))
@examples[#:hidden #:eval posn-eval
(require racket/match racket/stream (for-syntax racket/base))]
@title[#:tag "define-struct"]{Defining Structure Types: @racket[struct]}
@guideintro["define-struct"]{@racket[struct]}
@defform/subs[(struct id maybe-super (field ...)
struct-option ...)
([maybe-super code:blank
super-id]
[field field-id
[field-id field-option ...]]
[struct-option #:mutable
(code:line #:super super-expr)
(code:line #:inspector inspector-expr)
(code:line #:auto-value auto-expr)
(code:line #:guard guard-expr)
(code:line #:property prop-expr val-expr)
(code:line #:transparent)
(code:line #:prefab)
(code:line #:sealed)
(code:line #:authentic)
(code:line #:name name-id)
(code:line #:extra-name name-id)
(code:line #:constructor-name constructor-id)
(code:line #:extra-constructor-name constructor-id)
(code:line #:reflection-name symbol-expr)
(code:line #:methods gen:name-id method-defs)
#:omit-define-syntaxes
#:omit-define-values]
[field-option #:mutable
#:auto]
[method-defs (definition ...)])]{
Creates a new @techlink{structure type} (or uses a pre-existing
structure type if @racket[#:prefab] is specified), and binds
transformers and variables related to the @tech{structure type}.
A @racket[struct] form with @math{n} @racket[field]s defines up
to @math{4+2n} names:
@itemize[
@item{@racketidfont{struct:}@racket[id], a @deftech{structure type
descriptor} value that represents the @tech{structure type}.}
@item{@racket[constructor-id] (which defaults to @racket[id]), a
@deftech{constructor} procedure that takes @math{m} arguments
and returns a new instance of the @tech{structure type}, where
@math{m} is the number of @racket[field]s that do not include
an @racket[#:auto] option.}
@item{@racket[name-id] (which defaults to @racket[id]),
a @tech{transformer} binding that encapsulates
information about the structure type declaration. This binding
is used to define subtypes, and it also works with the
@racket[shared] and @racket[match] forms. For detailed
information about the binding of @racket[name-id], see
@secref["structinfo"].
The @racket[constructor-id] and @racket[name-id] can be the same, in
which case @racket[name-id] performs both roles. In that case, the
expansion of @racket[name-id] as an expression produces an otherwise
inaccessible identifier that is bound to the constructor
procedure; the expanded identifier has a
@racket['constructor-for] property whose value is an identifier
that is @racket[free-identifier=?] to @racket[name-id] as well as
a syntax property accessible via
@racket[syntax-procedure-alias-property] with an identifier
that is @racket[free-identifier=?] to @racket[name-id].}
@item{@racket[id]@racketidfont{?}, a @deftech{predicate} procedure
that returns @racket[#t] for instances of the @tech{structure
type} (constructed by @racket[constructor-id] or the
@tech{constructor} for a subtype) and @racket[#f] for any other
value.}
@item{@racket[id]@racketidfont{-}@racket[field-id], for each
@racket[field]; an @deftech{accessor} procedure that takes an
instance of the @tech{structure type} and extracts the value
for the corresponding field.}
@item{@racketidfont{set-}@racket[id]@racketidfont{-}@racket[field-id]@racketidfont{!},
for each @racket[field] that includes a
@racket[#:mutable] option, or when the
@racket[#:mutable] option is specified as a
@racket[struct-option]; a @deftech{mutator} procedure that
takes an instance of the @tech{structure type} and a new field
value. The structure is destructively updated with the new
value, and @|void-const| is returned.}
]
If @racket[super-id] is provided, it must have a transformer binding
of the same sort bound to @racket[name-id] (see @secref["structinfo"]),
and it specifies a supertype for the structure type. Alternately,
the @racket[#:super] option can be used to specify an expression that
must produce a @tech{structure type descriptor}. See
@secref["structures"] for more information on structure subtypes
and supertypes. If both @racket[super-id] and @racket[#:super] are
provided, a syntax error is reported.
@examples[#:eval posn-eval
(struct document (author title content))
(struct book document (publisher))
(struct paper (journal) #:super struct:document)
]
If the @racket[#:mutable] option is specified for an individual
field, then the field can be mutated in instances of the structure
type, and a @tech{mutator} procedure is bound. Supplying
@racket[#:mutable] as a @racket[struct-option] is the same as
supplying it for all @racket[field]s. If @racket[#:mutable] is
specified as both a @racket[field-option] and @racket[struct-option],
a syntax error is reported.
@examples[#:eval posn-eval
(struct cell ([content #:mutable]) #:transparent)
(define a-cell (cell 0))
(set-cell-content! a-cell 1)
]
The @racket[#:inspector], @racket[#:auto-value], and @racket[#:guard]
options specify an inspector, value for automatic fields, and guard
procedure, respectively. See @racket[make-struct-type] for more
information on these attributes of a structure type. The
@racket[#:property] option, which can be supplied
multiple times, attaches a property value to the structure type; see
@secref["structprops"] for more information on properties. The
@racket[#:transparent] option is a shorthand for @racket[#:inspector
#f].
@examples[#:eval posn-eval
(struct point (x y) #:inspector #f)
(point 3 5)
(struct celsius (temp)
#:guard (λ (temp name)
(unless (and (real? temp) (>= temp -273.15))
(error "not a valid temperature"))
temp))
(eval:error (celsius -275))
]
@margin-note{Use the @racket[prop:procedure] property to implement an
@as-index{applicable structure}, use @racket[prop:evt] to create a
structure type whose instances are @tech{synchronizable events}, and
so on. By convention, property names start with @racketidfont{prop:}.}
The @racket[#:prefab] option obtains a @techlink{prefab} (pre-defined,
globally shared) structure type, as opposed to creating a new
structure type. Such a structure type is inherently transparent and
non-sealed, and it cannot have a guard or properties, so using @racket[#:prefab] with
@racket[#:transparent], @racket[#:inspector], @racket[#:guard],
@racket[#:property], @racket[#:sealed], @racket[#:authentic],
or @racket[#:methods] is a syntax error.
If a supertype is specified, it must also be a @tech{prefab} structure type.
@examples[#:eval posn-eval
(struct prefab-point (x y) #:prefab)
(prefab-point 1 2)
(prefab-point? #s(prefab-point 1 2))
]
The @racket[#:sealed] option is a shorthand for @racket[#:property
prop:sealed #t], which prevents the structure type from being
used as the supertype of another structure type. See
@racket[prop:sealed] for more information.
The @racket[#:authentic] option is a shorthand for @racket[#:property
prop:authentic #t], which prevents instances of the structure type
from being impersonated (see @racket[impersonate-struct]), chaperoned
(see @racket[chaperone-struct]), or acquiring a non-@tech{flat
contract} (see @racket[struct/c]). See @racket[prop:authentic] for
more information. If a supertype is specified, it must also have the
@racket[prop:authentic] property.
If @racket[name-id] is supplied via @racket[#:extra-name] and it is
not @racket[id], then both @racket[name-id] and @racket[id] are bound
to information about the structure type. Only one of
@racket[#:extra-name] and @racket[#:name] can be provided within a
@racket[struct] form, and @racket[#:extra-name] cannot be combined
with @racket[#:omit-define-syntaxes].
@examples[#:eval posn-eval
(struct ghost (color name) #:prefab #:extra-name GHOST)
(match (ghost 'red 'blinky)
[(GHOST c n) c])
]
If @racket[constructor-id] is supplied, then the @tech{transformer}
binding of @racket[name-id] records @racket[constructor-id] as the
constructor binding; as a result, for example, @racket[struct-out]
includes @racket[constructor-id] as an export. If
@racket[constructor-id] is supplied via
@racket[#:extra-constructor-name] and it is not @racket[id], applying
@racket[object-name] on the constructor produces the symbolic form of
@racket[id] rather than @racket[constructor-id]. If
@racket[constructor-id] is supplied via @racket[#:constructor-name]
and it is not the same as @racket[name-id], then @racket[name-id] does not serve
as a constructor, and @racket[object-name] on the constructor produces
the symbolic form of @racket[constructor-id]. Only one of
@racket[#:extra-constructor-name] and @racket[#:constructor-name]
can be provided within a @racket[struct] form.
@examples[#:eval posn-eval
(struct color (r g b) #:constructor-name -color)
(struct rectangle (w h color) #:extra-constructor-name rect)
(rectangle 13 50 (-color 192 157 235))
(rect 50 37 (-color 35 183 252))
]
If @racket[#:reflection-name symbol-expr] is provided, then
@racket[symbol-expr] must produce a symbol that is used to identify
the structure type in reflective operations such as
@racket[struct-type-info]. It corresponds to the first argument of
@racket[make-struct-type]. Structure printing uses the reflective
name, as do the various procedures that are bound by @racket[struct].
@examples[#:eval posn-eval
(struct circle (radius) #:reflection-name '<circle>)
(circle 15)
(eval:error (circle-radius "bad"))
]
If @racket[#:methods gen:name-id method-defs] is provided (potentially multiple times), then
@racket[gen:name-id] must be a transformer binding for the static
information about a generic interface produced by @racket[define-generics].
The @racket[method-defs] define the methods of the @racket[gen:name-id]
interface. A @racket[define/generic] form or auxiliary definitions
and expressions may also appear in @racket[method-defs].
@examples[#:eval posn-eval
(struct constant-stream (val)
#:methods gen:stream
[(define (stream-empty? stream) #f)
(define (stream-first stream)
(constant-stream-val stream))
(define (stream-rest stream) stream)])
(stream-ref (constant-stream 'forever) 0)
(stream-ref (constant-stream 'forever) 50)
]
If the @racket[#:omit-define-syntaxes] option is supplied, then
@racket[name-id] (and @racket[id], if @racket[#:extra-name] is specified)
is not bound as a transformer. If the
@racket[#:omit-define-values] option is supplied, then none of the
usual variables are bound, but @racket[id] is bound. If both are
supplied, then the @racket[struct] form is equivalent to
@racket[(begin)].
@examples[#:eval posn-eval
(struct square (side) #:omit-define-syntaxes)
(eval:error
(match (square 5)
(code:comment "fails to match because syntax is omitted")
[(struct square x) x]))
(struct ellipse (width height) #:omit-define-values)
(eval:error ellipse-width)
]
@margin-note{
Expressions supplied to @racket[#:auto-value] are evaluated once and shared
between every instance of the structure type. In particular, updates to
a mutable @racket[#:auto-value] affect all current and future instances.
}
If @racket[#:auto] is supplied as a @racket[field-option], then the
@tech{constructor} procedure for the structure type does not accept an
argument corresponding to the field. Instead, the structure type's
automatic value is used for the field, as specified by the
@racket[#:auto-value] option, or as defaults to @racket[#f] when
@racket[#:auto-value] is not supplied. The field is mutable (e.g.,
through reflective operations), but a mutator procedure is bound only
if @racket[#:mutable] is specified.
If a @racket[field] includes the @racket[#:auto] option, then all
fields after it must also include @racket[#:auto], otherwise a syntax
error is reported. If any @racket[field-option] or
@racket[struct-option] keyword is repeated, other than
@racket[#:property], a syntax error is reported.
@examples[
#:eval posn-eval
(eval:no-prompt
(struct posn (x y [z #:auto #:mutable])
#:auto-value 0
#:transparent))
(posn 1 2)
(posn? (posn 1 2))
(posn-y (posn 1 2))
(posn-z (posn 1 2))
(eval:no-prompt
(struct color-posn posn (hue) #:mutable)
(define cp (color-posn 1 2 "blue")))
(color-posn-hue cp)
cp
(set-posn-z! cp 3)
]
For serialization, see @racket[define-serializable-struct].
@history[#:changed "6.9.0.4" @elem{Added @racket[#:authentic].}
#:changed "8.0.0.7" @elem{Added @racket[#:sealed].}]}
@defform[(struct-field-index field-id)]{
This form can only appear as an expression within a
@racket[struct] form; normally, it is used with
@racket[#:property], especially for a property like
@racket[prop:procedure]. The result of a @racket[struct-field-index]
expression is an exact, non-negative integer that corresponds to the
position within the structure declaration of the field named by
@racket[field-id].
@examples[
#:eval posn-eval
(eval:no-prompt
(struct mood-procedure (base rating)
#:property prop:procedure (struct-field-index base))
(define happy+ (mood-procedure add1 10)))
(happy+ 2)
(mood-procedure-rating happy+)
]}
@defform/subs[(define-struct id-maybe-super (field ...)
struct-option ...)
([id-maybe-super id
(id super-id)])]{
Like @racket[struct], except that the syntax for supplying a
@racket[super-id] is different, and a @racket[_constructor-id] that
has a @racketidfont{make-} prefix on @racket[id] is implicitly
supplied via @racket[#:extra-constructor-name] if neither
@racket[#:extra-constructor-name] nor @racket[#:constructor-name] is
provided.
This form is provided for backwards compatibility; @racket[struct] is
preferred.
@examples[
#:eval posn-eval
(eval:no-prompt
(define-struct posn (x y [z #:auto])
#:auto-value 0
#:transparent))
(make-posn 1 2)
(posn? (make-posn 1 2))
(posn-y (make-posn 1 2))
]}
@defform*[((struct/derived (id . rest-form)
id (field ...) struct-option ...)
(struct/derived (id . rest-form)
id super-id (field ...) struct-option ...))]{
The same as @racket[struct], but with an extra @racket[(id
. rest-form)] sub-form that is treated as the overall form for
syntax-error reporting and otherwise ignored. The only constraint on
the sub-form for error reporting is that it starts with @racket[id].
The @racket[struct/derived] form is intended for use by macros
that expand to @racket[struct].
@examples[
#:eval posn-eval
(eval:no-prompt
(define-syntax (fruit-struct stx)
(syntax-case stx ()
[(ds name . rest)
(with-syntax ([orig stx])
#'(struct/derived orig name (seeds color) . rest))])))
(fruit-struct apple)
(apple-seeds (apple 12 "red"))
(fruit-struct apple #:mutable)
(set-apple-seeds! (apple 12 "red") 8)
(code:comment "this next line will cause an error due to a bad keyword")
(eval:error (fruit-struct apple #:bad-option))
]
@history[#:added "7.5.0.16"]}
@defform[(define-struct/derived (id . rest-form)
id-maybe-super (field ...) struct-option ...)]{
Like @racket[struct/derived], except that the syntax for supplying a
@racket[super-id] is different, and a @racket[_constructor-id] that
has a @racketidfont{make-} prefix on @racket[id] is implicitly
supplied via @racket[#:extra-constructor-name] if neither
@racket[#:extra-constructor-name] nor @racket[#:constructor-name] is
provided. The @racket[define-struct/derived] form is intended for use by macros
that expand to @racket[define-struct].
@examples[
#:eval posn-eval
(eval:no-prompt
(define-syntax (define-xy-struct stx)
(syntax-case stx ()
[(ds name . rest)
(with-syntax ([orig stx])
#'(define-struct/derived orig name (x y) . rest))])))
(define-xy-struct posn)
(posn-x (make-posn 1 2))
(define-xy-struct posn #:mutable)
(set-posn-x! (make-posn 1 2) 0)
(code:comment "this next line will cause an error due to a bad keyword")
(eval:error (define-xy-struct posn #:bad-option))
]
@history[#:changed "7.5.0.16" @elem{Moved main description to @racket[struct/derived]
and replaced with differences.}]}
@; ----------------------------------------
@close-eval[posn-eval]