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:
parent
40c73b5992
commit
2842a17112
1 changed files with 206 additions and 123 deletions
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue