Records, extended

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

Creating type-specific functions

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

Passing a base type to a function

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.

Abstract record types

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 records can make OmniMark programs more robust, by preventing the creation of instances of types that should never be created.