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" with the base characteristics common to all publications:

  declare record publication
   field stream name
   field stream publisher

You can then create record types for specific types of publications by extending the publication type:

  declare record book
   extends publication
   field stream author variable
   field stream year-of-publication
   field stream ISBN
  
  declare record periodical
   extends publication
   field integer issues-per-year
   field stream editor-in-chief
   field stream 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
   extends book
   field stream genre
   field stream locale 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 pub-info 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
   field stream name
   field stream publisher
  
  declare record book
   extends publication
   field stream author variable
   field stream year-of-publication
   field stream ISBN
  
  declare record periodical
   extends publication
   field integer issues-per-year
   field stream editor-in-chief
   field stream ISPN
  
  define string source function pub-info
   value publication p
   as
      output p:name || "%n"
  
  process
     local publication pubs variable
     local book war-and-peace
     local periodical field-and-stream
  
     set war-and-peace:name to "War and Peace"
     set new war-and-peace:author to "Leo Tolstoy"
  
     set field-and-stream:name to "Field and Stream"
     set field-and-stream:issues-per-year to "12"
  
     output pub-info war-and-peace
     output pub-info 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" record to a shelf of type "book" or a shelf of type "publication":

  process
     local novel bedtime-reading
     local book work-reading
     local periodical bathroom-reading
     local publication reading-material variable
  
     set bedtime-reading:name to "The Sinister Pig"
     set bedtime-reading:publisher to "HarperCollins"
     set new bedtime-reading:author 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:locale to "Four Corners"
     set new bedtime-reading:locale 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-material to bedtime-reading
     set new reading-material to work-reading
     set new reading-material to bathroom-reading
  
     output "Stuff I'm currently reading:%n"
     repeat over 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-material
     output reading-material:name
     do select-type reading-material as r
     case novel
        output ", a " || r:genre || " novel%n"
     case book
        output "by "
        repeat over r:author as a
           output a
           output "," unless #last
        again
        output "%n"
     case periodical
        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 overloaded 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, 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
   field stream name
   field stream publisher
  
  declare record book
   extends publication
   field stream author variable
   field stream year-of-publication
   field stream ISBN
  
  declare record periodical
   extends publication
   field integer issues-per-year
   field stream editor-in-chief
   field stream ISPN
  
  declare record novel
   extends book
   field stream genre
   field stream locale variable
  
  define dynamic string source function display
   value publication p
   as
     output p:name || "%n"
  
  define overriding string source function display
   value novel n
   as
     output n:name
         || " a "
         || n:genre
         || " novel%n"
  
  define overriding string source function display
   value book b
   as
     output b:name
         || " by "
     repeat over b:author as a
        output a
        output "," unless #last
     again
     output "%n"
  
  define overriding string source function display
   value periodical p
   as
     output p:name
         || ", a periodical%n"
  
  process
     local novel bedtime-reading
     local book work-reading
     local periodical bathroom-reading
     local publication reading-material variable
  
     set bedtime-reading:name to "The Sinister Pig"
     set bedtime-reading:publisher to "HarperCollins"
     set new bedtime-reading:author 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:locale to "Four Corners"
     set new bedtime-reading:locale to "Mexican border"
  
     set work-reading:name to "Internet Programming with OmniMark"
     set new work-reading:author to "Mark Baker"
     ;initialize rest of work-reading
  
     set bathroom-reading:name to "Reader's Digest"
     ;initialize rest of bathroom-reading
  
     set new reading-material to bedtime-reading
     set new reading-material to work-reading
     set new reading-material to bathroom-reading
  
     repeat over 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
   into modifiable novel n
   as
     local novel x
     repeat
       ;populate fields of x
       set new n to x
     again

This works so long as you only pass shelves of type novel to the function. However, you may want to be able to pass shelves of type publication or type book to the function. The type novel is an extension of book and publication, so you can place a novel record on a book or publication shelf. However, you cannot pass a book or a publication shelf as a modifiable argument to a function that expects a shelf of type novel. For instance, if you passed it a shelf of type "publication", that shelf might contain a record of type "periodical". The function, however, thinks that the shelf is of type "novel", and a periodical cannot be used as a novel.

To write a function that add novels to a shelf, but that will accept a book or publication shelf as an argument, you must use a write only argument:

  define function add-novels
   into 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 variable of type novel, update its properties, and then add it to the write-only argument shelf.