1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-06-09 21:40:33 +02:00

improve documentation for structs

* doc/ref/api-compound.texi (Structures): Update to describe
  <standard-vtable>, to remove documentation for make-vtable-vtable, to
  describe meta-vtables, and to add a long example.
This commit is contained in:
Andy Wingo 2012-07-28 12:43:46 +02:00
parent 40c73b5992
commit 2842a17112

View file

@ -2370,11 +2370,12 @@ data abstractions, and for that purpose structures are useful. Indeed,
records in Guile are implemented with structures. records in Guile are implemented with structures.
@menu @menu
* Vtables:: * Vtables::
* Structure Basics:: * Structure Basics::
* Vtable Contents:: * Vtable Contents::
* Vtable Vtables:: * Meta-Vtables::
* Tail Arrays:: * Vtable Example::
* Tail Arrays::
@end menu @end menu
@node Vtables @node Vtables
@ -2447,11 +2448,9 @@ structure.
@example @example
(make-vtable "prpw" (make-vtable "prpw"
(lambda (struct port) (lambda (struct port)
(display "#<" port) (format port "#<~a and ~a>"
(display (struct-ref struct 0) port) (struct-ref struct 0)
(display " and " port) (struct-ref struct 1))))
(display (struct-ref struct 1) port)
(display ">" port)))
@end example @end example
@end deffn @end deffn
@ -2542,26 +2541,24 @@ Contents}, for more on vtables.
@node Vtable Contents @node Vtable Contents
@subsubsection Vtable Contents @subsubsection Vtable Contents
A vtable is itself a structure, with particular fields that hold A vtable is itself a structure. It has a specific set of fields
information about the structures to be created. These include the describing various aspects of its @dfn{instances}: the structures
fields of those structures, and the print function for them. The created from a vtable. Some of the fields are internal to Guile, some
variables below allow access to those fields. of them are part of the public interface, and there may be additional
fields added on by the user.
@deffn {Scheme Procedure} struct-vtable? obj Every vtable has a field for the layout of their instances, a field for
@deffnx {C Function} scm_struct_vtable_p (obj) the procedure used to print its instances, and a field for the name of
Return @code{#t} if @var{obj} is a vtable structure. the vtable itself. Access to the layout and printer is exposed directly
via field indexes. Access to the vtable name is exposed via accessor
Note that because vtables are simply structures with a particular procedures.
layout, @code{struct-vtable?} can potentially return true on an
application structure which merely happens to look like a vtable.
@end deffn
@defvr {Scheme Variable} vtable-index-layout @defvr {Scheme Variable} vtable-index-layout
@defvrx {C Macro} scm_vtable_index_layout @defvrx {C Macro} scm_vtable_index_layout
The field number of the layout specification in a vtable. The layout The field number of the layout specification in a vtable. The layout
specification is a symbol like @code{pwpw} formed from the fields specification is a symbol like @code{pwpw} formed from the fields
string passed to @code{make-vtable}, or created by string passed to @code{make-vtable}, or created by
@code{make-struct-layout} (@pxref{Vtable Vtables}). @code{make-struct-layout} (@pxref{Meta-Vtables}).
@example @example
(define v (make-vtable "pwpw" 0)) (define v (make-vtable "pwpw" 0))
@ -2572,12 +2569,6 @@ This field is read-only, since the layout of structures using a vtable
cannot be changed. cannot be changed.
@end defvr @end defvr
@defvr {Scheme Variable} vtable-index-vtable
@defvrx {C Macro} scm_vtable_index_vtable
A self-reference to the vtable, ie.@: a type @code{s} field. This is
used by C code within Guile and has no use at the Scheme level.
@end defvr
@defvr {Scheme Variable} vtable-index-printer @defvr {Scheme Variable} vtable-index-printer
@defvrx {C Macro} scm_vtable_index_printer @defvrx {C Macro} scm_vtable_index_printer
The field number of the printer function. This field contains @code{#f} The field number of the printer function. This field contains @code{#f}
@ -2612,125 +2603,217 @@ from @var{vtable}.
@end deffn @end deffn
@node Vtable Vtables @node Meta-Vtables
@subsubsection Vtable Vtables @subsubsection Meta-Vtables
As noted above, a vtable is a structure and that structure is itself As a structure, a vtable also has a vtable, which is also a structure.
described by a vtable. Such a ``vtable of a vtable'' can be created Structures, their vtables, the vtables of the vtables, and so on form a
with @code{make-vtable-vtable} below. This can be used to build sets tree of structures. Making a new structure adds a leaf to the tree, and
of related vtables, possibly with extra application fields. if that structure is a vtable, it may be used to create other leaves.
This second level of vtable can be a little confusing. The ball If you traverse up the tree of vtables, via calling
example below is a typical use, adding a ``class data'' field to the @code{struct-vtable}, eventually you reach a root which is the vtable of
vtables, from which instance structures are created. The current itself:
implementation of Guile's own records (@pxref{Records}) does something
similar, a record type descriptor is a vtable with room to hold the
field names of the records to be created from it.
@deffn {Scheme Procedure} make-vtable-vtable user-fields tail-size [print]
@deffnx {C Function} scm_make_vtable_vtable (user_fields, tail_size, print_and_init_list)
Create a ``vtable-vtable'' which can be used to create vtables. This
vtable-vtable is also a vtable, and is self-describing, meaning its
vtable is itself. The following is a simple usage.
@example @example
(define vt-vt (make-vtable-vtable "" 0)) scheme@@(guile-user)> (current-module)
(define vt (make-struct vt-vt 0 $1 = #<directory (guile-user) 221b090>
(make-struct-layout "pwpw")) scheme@@(guile-user)> (struct-vtable $1)
(define s (make-struct vt 0 123 456)) $2 = #<record-type module>
scheme@@(guile-user)> (struct-vtable $2)
(struct-ref s 0) @result{} 123 $3 = #<<standard-vtable> 12c30a0>
scheme@@(guile-user)> (struct-vtable $3)
$4 = #<<standard-vtable> 12c3fa0>
scheme@@(guile-user)> (struct-vtable $4)
$5 = #<<standard-vtable> 12c3fa0>
scheme@@(guile-user)> <standard-vtable>
$6 = #<<standard-vtable> 12c3fa0>
@end example @end example
@code{make-struct} is used to create a vtable from the vtable-vtable. In this example, we can say that @code{$1} is an instance of @code{$2},
The first initializer is a layout object (field @code{$2} is an instance of @code{$3}, @code{$3} is an instance of
@code{vtable-index-layout}), usually obtained from @code{$4}, and @code{$4}, strangely enough, is an instance of itself.
@code{make-struct-layout} (below). An optional second initializer is The value bound to @code{$4} in this console session also bound to
a printer function (field @code{vtable-index-printer}), used as @code{<standard-vtable>} in the default environment.
described under @code{make-vtable} (@pxref{Vtables}).
@sp 1 @defvr {Scheme Variable} <standard-vtable>
@var{user-fields} is a layout string giving extra fields to have in A meta-vtable, useful for making new vtables.
the vtables. A vtable starts with some base fields as per @ref{Vtable @end defvr
Contents}, and @var{user-fields} is appended. The @var{user-fields}
start at field number @code{vtable-offset-user} (below), and exist in
both the vtable-vtable and in the vtables created from it. Such
fields provide space for ``class data''. For example,
@example All of these values are structures. All but @code{$1} are vtables. As
(define vt-of-vt (make-vtable-vtable "pw" 0)) @code{$2} is an instance of @code{$3}, and @code{$3} is a vtable, we can
(define vt (make-struct vt-of-vt 0)) say that @code{$3} is a @dfn{meta-vtable}: a vtable that can create
(struct-set! vt vtable-offset-user "my class data") vtables.
@end example
@var{tail-size} is the size of the tail array in the vtable-vtable With this definition, we can specify more precisely what a vtable is: a
itself, if @var{user-fields} specifies a tail array. This should be 0 vtable is a structure made from a meta-vtable. Making a structure from
if nothing extra is required or the format has no tail array. The a meta-vtable runs some special checks to ensure that the first field of
tail array field such as @samp{pW} holds the tail array size, as the structure is a valid layout. Additionally, if these checks see that
usual, and is followed by the extra space. the layout of the child vtable contains all the required fields of a
vtable, in the correct order, then the child vtable will also be a
meta-table, inheriting a magical bit from the parent.
@example @deffn {Scheme Procedure} struct-vtable? obj
(define vt-vt (make-vtable-vtable "pW" 20)) @deffnx {C Function} scm_struct_vtable_p (obj)
(define my-vt-tail-start (1+ vtable-offset-user)) Return @code{#t} if @var{obj} is a vtable structure: an instance of a
(struct-set! vt-vt (+ 3 my-vt-tail-start) "data in tail") meta-vtable.
@end example
The optional @var{print} argument is used by @code{display} and
@code{write} (etc) to print the vtable-vtable and any vtables created
from it. It's called as @code{(@var{print} vtable port)} and should
look at @var{vtable} and write to @var{port}. The default is the
usual structure print function, which just gives machine addresses.
@end deffn @end deffn
@code{<standard-vtable>} is a root of the vtable tree. (Normally there
is only one root in a given Guile process, but due to some legacy
interfaces there may be more than one.)
The set of required fields of a vtable is the set of fields in the
@code{<standard-vtable>}, and is bound to @code{standard-vtable-fields}
in the default environment. It is possible to create a meta-vtable that
with additional fields in its layout, which can be used to create
vtables with additional data:
@example
scheme@@(guile-user)> (struct-ref $3 vtable-index-layout)
$6 = pruhsruhpwphuhuhprprpw
scheme@@(guile-user)> (struct-ref $4 vtable-index-layout)
$7 = pruhsruhpwphuhuh
scheme@@(guile-user)> standard-vtable-fields
$8 = "pruhsruhpwphuhuh"
scheme@@(guile-user)> (struct-ref $2 vtable-offset-user)
$9 = module
@end example
In this continuation of our earlier example, @code{$2} is a vtable that
has extra fields, because its vtable, @code{$3}, was made from a
meta-vtable with an extended layout. @code{vtable-offset-user} is a
convenient definition that indicates the number of fields in
@code{standard-vtable-fields}.
@defvr {Scheme Variable} standard-vtable-fields
A string containing the orderedq set of fields that a vtable must have.
@end defvr
@defvr {Scheme Variable} vtable-offset-user
The first index in a vtable that is available for a user.
@end defvr
@deffn {Scheme Procedure} make-struct-layout fields @deffn {Scheme Procedure} make-struct-layout fields
@deffnx {C Function} scm_make_struct_layout (fields) @deffnx {C Function} scm_make_struct_layout (fields)
Return a structure layout symbol, from a @var{fields} string. Return a structure layout symbol, from a @var{fields} string.
@var{fields} is as described under @code{make-vtable} @var{fields} is as described under @code{make-vtable}
(@pxref{Vtables}). An invalid @var{fields} string is an error. (@pxref{Vtables}). An invalid @var{fields} string is an error.
@example
(make-struct-layout "prpW") @result{} prpW
(make-struct-layout "blah") @result{} ERROR
@end example
@end deffn @end deffn
@defvr {Scheme Variable} vtable-offset-user With these definitions, one can define @code{make-vtable} in this way:
@defvrx {C Macro} scm_vtable_offset_user
The first field in a vtable which is available for application use.
Such fields only exist when specified by @var{user-fields} in
@code{make-vtable-vtable} above.
@end defvr
@sp 1 @example
Here's an extended vtable-vtable example, creating classes of (define* (make-vtable fields #:optional printer)
``balls''. Each class has a ``colour'', which is fixed. Instances of (make-struct/no-tail <standard-vtable>
those classes are created, and such each such ball has an ``owner'', (make-struct-layout fields)
which can be changed. printer))
@end example
@lisp
(define ball-root (make-vtable-vtable "pr" 0))
(define (make-ball-type ball-color) @node Vtable Example
(make-struct ball-root 0 @subsubsection Vtable Example
(make-struct-layout "pw")
(lambda (ball port)
(format port "#<a ~A ball owned by ~A>"
(color ball)
(owner ball)))
ball-color))
(define (color ball)
(struct-ref (struct-vtable ball) vtable-offset-user))
(define (owner ball)
(struct-ref ball 0))
(define red (make-ball-type 'red)) Let us bring these points together with an example. Consider a simple
(define green (make-ball-type 'green)) object system with single inheritance. Objects will be normal
structures, and classes will be vtables with three extra class fields:
the name of the class, the parent class, and the list of fields.
(define (make-ball type owner) (make-struct type 0 owner)) So, first we need a meta-vtable that allocates instances with these
extra class fields.
(define ball (make-ball green 'Nisse)) @example
ball @result{} #<a green ball owned by Nisse> (define <class>
@end lisp (make-vtable
(string-append standard-vtable-fields "pwpwpw")
(lambda (x port)
(format port "<<class> ~a>" (class-name x)))))
(define (class? x)
(and (struct? x)
(eq? (struct-vtable x) <class>)))
@end example
To make a structure with a specific meta-vtable, we will use
@code{make-struct/no-tail}, passing it the computed instance layout and
printer, as with @code{make-vtable}, and additionally the extra three
class fields.
@example
(define (make-class name parent fields)
(let* ((fields (compute-fields parent fields))
(layout (compute-layout fields)))
(make-struct/no-tail <class>
layout
(lambda (x port)
(print-instance x port))
name
parent
fields)))
@end example
Instances will store their associated data in slots in the structure: as
many slots as there are fields. The @code{compute-layout} procedure
below can compute a layout, and @code{field-index} returns the slot
corresponding to a field.
@example
(define-syntax-rule (define-accessor name n)
(define (name obj)
(struct-ref obj n)))
;; Accessors for classes
(define-accessor class-name (+ vtable-offset-user 0))
(define-accessor class-parent (+ vtable-offset-user 1))
(define-accessor class-fields (+ vtable-offset-user 2))
(define (compute-fields parent fields)
(if parent
(append (class-fields parent) fields)
fields))
(define (compute-layout fields)
(make-struct-layout
(string-concatenate (make-list (length fields) "pw"))))
(define (field-index class field)
(list-index (class-fields class) field))
(define (print-instance x port)
(format port "<~a" (class-name (struct-vtable x)))
(for-each (lambda (field idx)
(format port " ~a: ~a" field (struct-ref x idx)))
(class-fields (struct-vtable x))
(iota (length (class-fields (struct-vtable x)))))
(format port ">"))
@end example
So, at this point we can actually make a few classes:
@example
(define-syntax-rule (define-class name parent field ...)
(define name (make-class 'name parent '(field ...))))
(define-class <surface> #f
width height)
(define-class <window> <surface>
x y)
@end example
And finally, make an instance:
@example
(make-struct/no-tail <window> 400 300 10 20)
@result{} <<window> width: 400 height: 300 x: 10 y: 20>
@end example
And that's that. Note that there are many possible optimizations and
feature enhancements that can be made to this object system, and the
included GOOPS system does make most of them. For more simple use
cases, the records facility is usually sufficient. But sometimes you
need to make new kinds of data abstractions, and for that purpose,
structs are here.
@node Tail Arrays @node Tail Arrays
@subsubsection Tail Arrays @subsubsection Tail Arrays