You can create records that are extensions of other records.
This is useful if you have records of different types that share common characteristics.
For example, you can declare a record type publication-type
with the base characteristics common to all publications:
declare record publication-type field string name field string publisher
You can then create record types for specific types of publications by extending the publication type:
declare record book-type extends publication-type field string authors variable field string year-of-publication field string ISBN declare record periodical-type extends publication-type field integer issues-per-year field string editor-in-chief field string ISPN
You can use an extended record type as the base type for creating a more extended (and therefore more specialized type):
declare record novel-type extends book-type field string genre field string locales variable
A shelf of an extended type can be used in place of a shelf of its base type (or any of its ancestral base types)
in most circumstances in which the base type can be used.
For instance, if you write a function to print the details of a publication, you can also pass a book, or novel to that function.
In the following example, the string source
function publication-information
takes a publication,
but the program passes a book and a periodical to it.
Since a book and a periodical have all the fields of a publication, they can be used as a publication:
declare record publication-type field string name field string publisher declare record book-type extends publication-type field string authors variable field string year-of-publication field string ISBN declare record periodical-type extends publication-type field integer issues-per-year field string editor-in-chief field string ISPN define string source function publication-information (value publication-type p) as output p:name || "%n" process local publication-type publications variable local book-type war-and-peace local periodical-type field-and-stream set war-and-peace:name to "War and Peace" set new war-and-peace:authors to "Leo Tolstoy" set field-and-stream:name to "Field and Stream" set field-and-stream:issues-per-year to 12 output publication-information (war-and-peace) output publication-information (field-and-stream)
Since a record of an extended type can be used as a record of a base type,
you can assign a record of an extended type to a shelf of its base type or to a shelf of any of its ancestor types.
Thus you can assign a novel-type
record to a shelf of type book-type
or a shelf of type publication-type
:
process local novel-type bedtime-reading local book-type work-reading local periodical-type bathroom-reading local publication-type reading-materials variable set bedtime-reading:name to "The Sinister Pig" set bedtime-reading:publisher to "HarperCollins" set new bedtime-reading:authors to "Tony Hillerman" set bedtime-reading:year-of-publication to "2003" set bedtime-reading:ISBN to "006019443X" set bedtime-reading:genre to "mystery" set new bedtime-reading:locales to "Four Corners" set new bedtime-reading:locales to "Mexican border" set work-reading:name to "Internet Programming with OmniMark" ;initialize rest of work-reading set bathroom-reading:name to "Reader's Digest" ;initialize rest of bathroom-reading set new reading-materials to bedtime-reading set new reading-materials to work-reading set new reading-materials to bathroom-reading output "Stuff I'm currently reading:%n" repeat over reading-materials as reading-material output reading-material:name || "%n" again
If you have a shelf of a base type that contains records of extended types,
you can safely address the fields that belong to the base type and are therefore common to all extended types.
If you want to address fields that are specific to any of the extended types,
you first need to determine the precise type of each record on the shelf.
You can do this using do select-type
:
repeat over reading-materials as reading-material output reading-material:name do select-type reading-material as r case novel-type output ", a " || r:genre || " novel%n" case book-type output "by " repeat over r:authors as author output author output "," unless #last again output "%n" case periodical-type output ", a periodical edited by " || r:editor-in-chief else output "%n" done again
Rather than using do select-type
every time you need to determine the exact type of a record,
you can create a collection of type-specific functions using the dynamic
and overriding
keywords.
To do this you define a dynamic function
for the base type
and an overriding function
for each of the extensions of the base type.
The following code defines several extensions of publication-type
, and a display
function for each one.
The repeat
loop at the end of the code calls the display
function for each items on a shelf of publications.
The appropriate overriding
function is called dynamically based on the type of each individual item on the shelf:
declare record publication-type field string name field string publisher declare record book-type extends publication-type field string authors variable field string year-of-publication field string ISBN declare record periodical-type extends publication-type field integer issues-per-year field string editor-in-chief field string ISPN declare record novel-type extends book-type field string genre field string locales variable define dynamic string source function display (value publication-type p) as output p:name || "%n" define overriding string source function display (value novel-type n) as output n:name || " a " || n:genre || " novel%n" define overriding string source function display (value book-type b) as output b:name || " by " repeat over b:authors as author output author output "," unless #last again output "%n" define overriding string source function display (value periodical-type p) as output p:name || ", a periodical%n" process local novel-type bedtime-reading local book-type work-reading local periodical-type bathroom-reading local publication-type reading-materials variable set bedtime-reading:name to "The Sinister Pig" set bedtime-reading:publisher to "HarperCollins" set new bedtime-reading:authors to "Tony Hillerman" set bedtime-reading:year-of-publication to "2003" set bedtime-reading:ISBN to "006019443X" set bedtime-reading:genre to "mystery" set new bedtime-reading:locales to "Four Corners" set new bedtime-reading:locales to "Mexican border" set work-reading:name to "Internet Programming with OmniMark" set new work-reading:authors to "Mark Baker" ;initialize rest of work-reading set bathroom-reading:name to "Reader's Digest" ;initialize rest of bathroom-reading set new reading-materials to bedtime-reading set new reading-materials to work-reading set new reading-materials to bathroom-reading repeat over reading-materials as reading-material output display (reading-material) again
Suppose you want to write a function that will add novels to a shelf.
You could write the function like this:
define function add-novels (modifiable novel-type n) as local novel-type x repeat ;populate fields of x set new n to x again
This works so long as you only pass shelves of type novel-type
to the function.
However, you may want to be able to pass shelves of type publication-type
or type book-type
to the function.
The type novel-type
is an extension of book-type
and publication-type
,
so you can place a novel-type
record on a book-type
or publication-type
shelf.
However, you cannot pass a book-type
or a publication-type
shelf as a modifiable
argument
to a function that expects a shelf of type novel-type
.
For instance, if you passed it a shelf of type publication-type
,
that shelf might contain a record of type periodical-type
.
The function, however, thinks that the shelf is of novel-type
., and a periodical-type
cannot be used as a novel-type
.
To write a function that adds novels to a shelf,
but that will accept a book-type
or publication-type
shelf as an argument,
you must use a write-only
argument:
define function add-novels (write-only novel n) as local novel x repeat ;populate fields of x set new n to x again
With a write-only
argument the function is forbidden to perform any operation on the shelf which is specific
to the type of the individual items on the shelf.
It cannot read or write the fields of individual records or even inquire about their type using do select-type
.
All it can do is add items to the shelf, replace an existing item with a new item, or delete an item.
The only way to add a new novel to a write-only
argument shelf, therefore,
is to create a local
shelf of type novel, update its properties, and then add it to the write-only
argument shelf.
An abstract record type is a record type that cannot be instantiated: it can only be used as a base for extending to
other record types.
In our examples above, we never instantiated a record of type publication-type
.
It is therefore a prime candidate for being declared as an abstract record type.
We do this by prefixing the keyword record
in the record type declaration by the keyword abstract
:
declare abstract record publication-type field string name field string publisher
This informs OmniMark that the type publication-type
should never be instantiated, and an error
will be thrown (either at compile-time or at run-time, depending on the situation) should any attempt be made to create
an instance of type publication-type
.
For example, the following code would fail (at compile-time)
process local publication-type publications variable new publications
since the operation new
will attempt to create an instance of type publication-type
.
However, the following code will succeed, since the new item on the shelf publications
will be made to
point to an instance of type book-type
:
process local publication-type publications variable local book-type book set new publications to book
Judicious use of abstract record
s can make OmniMark programs more robust, by preventing the
creation of instances of types that should never be created.