OmniMark provides a number of different simple data types, such as
integer
, switch
, and string
. Shelf items of these types, however, can only hold a single
value. In many cases you may want a shelf item that can hold multiple related values. Examples include shelves
that describe the metadata fields attached to a document or shelves that describe a point on a two-dimensional
grid.
Here is how you would declare a record
type to hold the metadata for a document:
declare record metadata-type field string author field string title field string publisher field integer year field switch in-print initial { true }
Each field
in the record
is itself of a particular type and is declared just as a local
shelf of that type would be declared, substituting the word field
for the word local
.
However, the record declaration by itself only defines a type. It does not create an actual shelf. Even though you can declare initial values for the fields, this only specifies what the initial values of those fields will be when an actual record is created.
To create an actual shelf of type metadata-type, you use a standard local
or global
declaration:
process local metadata-type document-information
Once you have a shelf of type metadata-type, you can read and write the individual fields of the
record instance. To address a particular field of a record instance, you use the shelf name, followed by a colon,
followed by the field name:
process local metadata-type document-information set document-information:author to "Sir Arthur Conan Doyle" set document-information:title to "The Hound of the Baskervilles" set document-information:publisher to "George Newnes" set document-information:year to 1902 set author-name to document-information:author output document-information:title
You can pass a record instance to a function:
define string source function format-metadata (value metadata-type x) as output "<H1>%n" || x:title || "</H1><P><B>by " || x:author || "</B></P>%n<P>" || x:publisher || " (" || "d" % x:year || ") " output "<I>out of print</I>" unless x:in-print output "</P>"
You can also return a record instance from a function:
define metadata-type function get-document-information () as local metadata-type x set x:author to "Sir Arthur Conan Doyle" set x:title to "The Hound of the Baskervilles" set x:publisher to "George Newnes" set x:year to 1902 return x
You can initialize a record instance by writing a function that takes the field values as arguments and
returns a record instance:
define point-type function make-point (value integer x, value integer y) as local point-type a set a:x to x set a:y to y return a
You can then call this function to initialize a record instance, either when the shelf is declared,
or in the body of a rule or function:
process local point-type foo local point-type bar initial { make-point (12, 32) } set foo to make-point (14, 81)
You will frequently want to convert the value of a record to text for output. If you usually want the output
to take the same format, you can write a conversion function to convert from a
record instance to a string
. The following conversion function converts a point-type record
instance to a comma-separated pair of values:
define string conversion-function value point-type p as return "d" % p:x || "," || "d" % p:y
Once this function is part of your program you can output a string
representation of a point like
this:
process local point-type foo initial { make-point (23, 86) } output foo
A record may be used to represent mathematical concepts, such as a point, and therefore it may be desirable to
perform mathematical operations on records. For example, you may want to add two instances of the type point-type together. You could write a function specifically to add point-types:
define point-type function add-points (value point-type a, value point-type b) as local point-type c set c:x to a:x + b:x set c:y to a:y + b:y return c
However, it would be more elegant if you could use the OmniMark +
operator to add instances of
point-types. This can be done by overloading OmniMark's +
operator:
define overloaded point-type infix-function value point-type a + value point-type b as local point-type c set c:x to a:x + b:x set c:y to a:y + b:y return c
Once this function is defined, you can add two point-type instances together like this:
process local point-type foo initial { make-point (23, 86) } local point-type bar initial { make-point (74, 98) } local point-type baz set baz to foo + bar
While you can treat record shelves like regular shelves for most purposes, there are some important
differences. Record shelf items are not in fact records themselves, the way an integer
shelf item really
is an integer
. A record shelf item is actually a reference to a record instance.
As long as you address the individual fields of a record, this makes no difference. The difference comes when
you use a record shelf item alone. Consider what happens in the following code:
process local integer i initial { 2 } local integer j initial { 4 } set j to i set i to 5 output "d" % j
As expected, this program outputs 2, the last value assigned to the shelf j
.
Now consider this program:
declare record point-type field integer x field integer y process local point-type alpha local point-type omega set alpha:x to 2 set alpha:y to 4 set omega:x to 3 set omega:y to 9 set omega to alpha set alpha:x to 12 set alpha:y to 15 output "d" % omega:x || "|" || "d" % omega:y
You might expect the output of this program to be 2|4 since that is the set of values in the record alpha when we assigned alpha to omega. However, if you run the program you will see that the actual output is 12|15, the last values assigned to alpha.
This happens because alpha and omega are both references to record
instances, not the names of the records themselves. When we assigned alpha to omega
therefore, we made the shelf omega
reference the same record instance as the shelf
alpha
. That left us with two references to one record instance and no references to the other
record instance. When we changed the values of the record instance referenced by alpha, we
were also changing the values of the record instance referenced by omega, since both
alpha and omega
reference the same
instance.
Since there were no references to the orphaned record instance that used to be referenced by omega, that record instance was no longer available to the program. Sooner or later, it will be cleaned up and disposed of automatically by OmniMark's garbage collector.
The fact that records are references has important consequences for how you compare two records.
But if set
copies references to records, how do you copy the values of one record instance to
another? One way is to do it field
by field
:
set alpha:x to 2 set alpha:y to 4 set omega:x to 3 set omega:y to 9 set omega:x to alpha:x set omega:y to alpha:y
However, this is cumbersome. Another approach is to write a function to copy instance of point-types:
define function copy-point (value point-type a, value point-type b) as set b:x to a:x set b:y to a:y process ; ... copy-point (alpha, omega)
You may notice something odd about this function: Both a and b are passed
to copy-point as value
arguments and value
arguments cannot be modified. Yet we are
changing the values of record b. Why does this work? It works because a
and
b
are both references to record instances, not records themselves. It is the references that are
passed by value
and the
references are not being changed, only the record instances that they refer to.