Functions: shelf-class

Whereas a value-returning function returns a single value, a shelf-class function is a function that returns an entire shelf.

Definitions and General Use

A shelf-class function is defined by prefixing its return type by one of the three shelf class keywords: modifiable, read-only, or write-only. For example,

  define read-only integer function
     some-primes ()
  as
     return { 2, 3, 5 with key "third", 7, 11, 13, 17 }
          
defines a function some-primes that returns a read-only shelf of integers, in this case a shelf literal that contains a few prime numbers. Since the function was defined read-only, the shelf's items will be readable at the call point, but not modifiable. Only prefix functions can be shelf-class: infix-functions and conversion-functions cannot. However, like any other prefix function, a shelf-class function can be both overloaded and dynamic/overriding.

The result of a shelf-class function can be used like any other shelf, within the restrictions of the function's class. For example some-primes can be used in a repeat over loop to output the first few prime numbers:

  process
     repeat over some-primes () as p
        output "d" % p || "%n"
     again
          
The shelf returned from a shelf-class function can also be used in the header of a using scope, among other places. In addition, it can be indexed to extract a single item from the returned shelf:
  process
     output "The second prime is: " || "d" % some-primes ()[2] || "%n"
          
If keyed indexing is used, the key must exist on the referenced shelf:
  process
     output "The third prime is: " || "d" % some-primes (){"third"} || "%n"
          
A shelf-class function can also be passed as an argument to a function that takes a shelf-class argument: for example,
  define string source function
     emit (read-only integer i)
  as
     repeat over i as i
        output "d" % i || "%n"
     again
  
  
  process
     output emit (some-primes ())
          
However, since some-primes was defined as a read-only shelf-class function, it cannot be used as an argument to a function expecting either a modifiable or write-only shelf class argument:
  define function
     wrong (modifiable integer i)
  elsewhere
  
  
  process
     wrong (some-primes ()) ; ERROR!
          

Shelves and Shelf References

A shelf-class function returns a reference to an existing shelf. This can have surprising results. For example the following shelf-class function returns a reference to a global:

  global string s initial { "Hello, World!", "Salut, Monde!" }
  
  define modifiable string function
     f ()
  as
     return s
          
If the caller modifies the reference returned by the function f, the modifications are reflected in the global shelf s:
  process
     copy string { "Hola, Mundo!", "Halo, Welt!" } to f ()
          
Following the execution of this process rule, the global s contains the two items Hola, Mundo! and Halo, Welt!, rather than the initial Hello, World! and Salut, Monde!.

Class and Type of a Shelf-Class Function

As indicated previously, the class of a shelf-class function impacts how the caller can use the shelf reference returned from a shelf-class function. It also impacts which shelves a shelf-class function is allowed to return. For example, a shelf-class function defined modifiable cannot return a reference to a constant, since the items of a shelf declared constant are not modifiable. For the same reason, a shelf-class function defined modifiable cannot return a reference to one of its read-only arguments:

  define modifiable string function
     f (read-only string s)
  as
     return s ; ERROR!
          

The rules for returning from a shelf-class function are:

  • a shelf-class function of any class can return a local or global shelf of the appropriate type, assuming the local or global is statically in-scope at the point of return,
  • a shelf-class function of any class can return a record field of the appropriate type, assuming the record that owns the field is statically in-scope at the point of return,
  • a shelf-class function of any class can return a shelf literal, assuming the shelf literal items are of the appropriate type or can be converted to the appropriate type,
  • a shelf-class function of class read-only can return a read-only or modifiable function argument of the appropriate type, or a constant of the appropriate type,
  • a shelf-class function of class modifiable can return a modifiable function argument of the appropriate type, and
  • a shelf-class function of class write-only can return a write-only or modifiable function argument of the appropriate type.

In this list, the term appropriate type is used rather than identical type, because the type of the shelf reference returned by a shelf-class function does not need to match the function's defined type identically: rather, the type of the shelf reference returned must be usable as the function's defined type, taking into account the class of the functions. The rules for the type of the shelf reference returned from a shelf-class function are:

  • the static type of a shelf reference returned from a read-only shelf-class function must be the same as, or any subtype of, the defined type of the function,
  • the static type of a shelf reference returned from a modifiable shelf-class function must be the same as the defined type of the function, and
  • the static type of a shelf reference returned from a write-only shelf-class function must be the same as, or any supertype of, the defined type of the function.

Read-Only

The shelf reference returned from a read-only shelf-class function can be used only in a context that does not modify the items of the shelf reference. For instance, a read-only shelf-class function can be used as a read-only shelf-class argument to a function call or throw, but not a modifiable or write-only shelf-class argument. A read-only shelf-class function can also be used on the left-hand side of the copy action:

  process
     local integer i variable
  
     copy some-primes () to i
          
since copy does not modify its left-hand shelf reference. In this example, the read-only restrictions on some-primes do not apply to the shelf i, since these are two separate shelf references: they simply contain items of the same value. Additionally, since some-primes was defined read-only, the previous example cannot be written to use copy-clear:
  process
     local integer i variable
  
     copy-clear some-primes () to i ; ERROR!
          
since copy-clear must modify (that is, clear) its left-hand shelf reference once the copy has been performed.

Write-Only

The shelf reference returned from a write-only shelf-class function can be used only in a context that does not require the the items of the shelf reference to be readable. For instance, a write-only shelf-class function can be used as a write-only shelf-class argument to a function call or throw, but not a modifiable or read-only shelf-class argument. A write-only shelf-class function can also be used on the right-hand side of a copy or copy-clear action:

  global string s variable
  
  define write-only string function
     absorber ()
  as
     return s
  
  
  process
     local string t initial { "Hello, World!", "Salut, Monde!", "Hola, Mundo!" }
  
     copy t to absorber ()
          
Here, the items of the local string t are copied to the shelf reference returned by the function absorber which is in turn a reference to the global s, so as a result the items of the local t are copied to the global s.

Indexers and Heralded Arguments

The examples thus far have used parenthesized arguments when defining shelf-class functions. Like ordinary prefix functions, shelf-class functions can use heralded arguments as well; for instance

  define read-only string function
     casing of value string s
  as
     return { s, "ug" % s, "lg" % s }
          
However, there is a subtlety involved that bears mentioning: indexers bind more tightly than heralded arguments. So when calling this function, if an indexer is to be applied to the result, parentheses are required:
  process
     local string s initial { "Hello, World!", "Salut, Monde!", "Hola, Mundo!" }
  
     output casing of s[2] ; outputs "salut, monde!"
     output (casing of s)[2] ; outputs "HOLA, MUNDO!"
          

Shelves Exported as Constants

Shelf-class functions can be used to export objects from a module that are constant to the importer, but modifiable by the module. A module simply has to export a read-only shelf-class function that returns a global which is itself not exported from the module:

  module 
     shared as "exporting shelves read-only"
  
  global string private-strings initial { "Hello, World!", "Salut, Monde!" }
  
  export read-only string function
     public-strings
  as
     return private-strings
          
Subsequently, an importer of this module can read the values of the shelf private-strings via a call to public-strings, but since the latter is a read-only shelf-class function, the importer cannot modify the contents of the shelf, while the module itself retains the ability to modify the shelf's contents.