define overloaded function, dynamic, overriding

declaration/definition

Syntax
define overloaded result-type? function 
   function-name argument-list
((as function-body) | elsewhere)

define overloaded? dynamic result-type? function 
   function name argument-list
((as function-body) | elsewhere)

define overriding result-type? function 
   function name argument-list
((as function-body) | elsewhere)


Purpose

You can write overloaded functions that share a common name but operate on different data types. You can also overload many existing OmniMark operators and keywords.

An overloaded function resembles a regular function or infix function in every respect, except that the definition of the function must include the keyword overloaded following the word define. For instance, here is an overloaded infix function that defines an addition operator for working with records of type point:

  declare record point
     field integer x
     field integer y
  
  define overloaded point infix-function
        value point a
     +
        value point b
  as
     local point c
  
     set c:x to a:x + b:x
     set c:y to a:y + b:y
  
     return c
  
  
  process
     local point foo
     local point bar
     local point baz
  
     set foo:x to 7
     set foo:y to 9
  
     set bar:x to 2
     set bar:y to 3
  
     set baz to foo + bar
  
     output "baz:x = " || "d" % baz:x
         || "%n"
         || "baz:y = " || "d" % baz:y

How overloaded functions are selected

When you write overloaded functions, you create two or more functions with the same name. OmniMark will select the function to execute based on the type of the arguments passed to the function. All overloadings of the same function name must be unique, including those that overload OmniMark keywords.

Not all argument positions are used in selecting overloaded functions. For a normal ("prefix") function, only the first argument position is used. Each overloading of a normal function must have a distinct type for the first argument. Subsequent arguments may differ in type and number, and in the heralds that separate them, but these differences are not used in selecting which overloading to execute. For an infix function both argument positions are used.

Overloading and data type conversion

In normal (non-overloaded) argument positions, if an argument of one type is expected, and an argument of another type is supplied, the data will be converted automatically to the expected type, as long as OmniMark supports the conversion, or an appropriate conversion function exists. In the case of the first argument of an overloaded function, or both arguments of an overloaded infix function, automatic conversion cannot take place, since the type of the data passed in those positions is used to select which version of the function to execute.

This means that when you write a set of overloaded functions, you have to make sure that you account for every type of data that might be passed to that function. In the case of adding two points, as in the example above, the operation is not intended to work with anything but points, and adding a point to an integer will result in an error, which is appropriate behavior.

However, you might also decide to allow points to be referred to as strings in the form "29,76". You could then further overload the + operator to add strings to points and points to string. (This example includes a conversion function to convert strings of the specified format to points):

  define point conversion-function value string coordinates
  as
     local point a
  
     assert coordinates matches (digit+ "," digit+ =|)
            message "String " || coordinates || " is not a valid set of coordinates."
  
     set a:x to coordinates take digit+
     set a:y to coordinates drop (digit+ ",")
  
     return a
  
  
  define overloaded point infix-function
        value (point | string into point) a
     +
        value point b
  as
     local point c
  
     set c:x to a:x + b:x
     set c:y to a:y + b:y
  
     return c
  
  
  define overloaded point infix-function
        value point a
     +
        value (string into point) b
  as
     local point c
  
     set c:x to a:x + b:x
     set c:y to a:y + b:y
  
     return c
  
  
  process
     local point foo initial {"23,86"}
     local point bar
  
     set bar to "123,678"
  
     output foo + bar
         || "%n"
         || foo + "176,34"
         || "%n"
         || "7,9" + bar
         || "%n"
         || point "7,9" + "176,34"

These examples demonstrate the use of the into keyword, which is used to force the conversion of data into the appropriate format for the function. The expression (string into point) means that the function accepts a string in this argument position, but then converts it into a point (by calling the string to point conversion function).

The expression (point | string into point) means that the function accepts either a point or a string in this argument position, and, if the argument is not a point already, converts it into a point.

In both these cases, the parentheses are required. You can combine any number of different argument types with | characters. You can have multiple types specified in both the first and second argument position of an infix function, however, you will need to make sure that none of pairings that result from doing this is either an existing overloading, or an overloading you do not want to create. The reason that there are two separate overloaded functions in the example above, rather than using (point | string into point) in both argument positions, is to avoid creating an overloading of string + string, which would be inappropriate, and would conflict with similar string + string overloadings in the similarly defined overloading of + for other record types.

Dynamically overloaded functions

The overloaded functions discussed above are statically overloaded. That is, the function to call is determined by the compiler at compile time based on the static type of the significant arguments. If you use extended records, however, you may create a situation in which the exact type of an argument cannot be known at compile time. If the argument type is a record type that has been extended, any of its extensions can be passed in place of the base type.

For example, the following code defines a record type point and an extension of that type called pixel:

  declare record point
     field integer x
     field integer y
  
  declare record pixel extends point
     field string color

A shelf of type point can contain records of either type point or type pixel. Using the overloaded + functions above, you could pass pixels to the functions, but the function invoked would be for points. It would add the point part of the pixel records and return a point, not a pixel. To create a function that will dynamically select a pixel or a point depending on what type is passed at runtime, you must create a dynamically overloaded function.

To create a dynamically overloaded function you must create a dynamic function for the base type and then create overriding functions for all the extensions of the base class that you want to handle differently.

  define dynamic string source function 
     display value point p
  as
     using p:x as x
        using p:y as y
           output "%d(x),%d(y)%n"
  
  
  define overriding string source function 
     display value pixel p
  as
     using p:x as x
        using p:y as y
           using p:color as c
              output "%d(x),%d(y):%g(c)%n"

You do not have to provide an overriding function for every extension of the base type. Any extensions for which you do not provide an overriding function will be handled by the dynamic function for the base type.

Statically and dynamically overloaded functions

You can overload a function both statically and dynamically. For example, you may want to provide display functions for different unrelated record types. To create a statically and dynamically overloaded function, include both the overloaded and dynamic keywords:

  define overloaded dynamic string source function 
     display value publication p
  as
     output p:name || "%n"
  
  
  define overloaded dynamic string source function 
     display value point p
  as
     using p:x as x
        using p:y as y
           output "%d(x),%d(y)%n"

OmniMark will determine which display function to call at compile time based on the static type of the first argument. (For this purpose, any type that is an extension of the base type will be treated as the base type.) At run time, any overriding functions available for the appropriate type will be selected. Note that the overriding functions are not declared as overloaded, but simply as overriding:

  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 pixel p
  as
     using p:x as x
        using p:y as y
           using p:color as c
              output "%d(x),%d(y):%g(c)%n"

Limitations on dynamic functions

The return type of an overriding function must be the same as the return type of the dynamic function that it overrides, or a type that can be used as the type of the dynamic function (that is, a type that is an extension of the type returned by the dynamic function).

Note that while statically overloaded functions with the same name can have different function signatures (different numbers and types of arguments and different argument heralds) overriding functions for the same base type must all have the same function signature as their base dynamic function. Only the names of the arguments can be different, not their number, type, heralds, or method of passing (value, etc.). (The type may be a type that is usable as the type defined in the dynamic function, according to the method used to pass the argument.)

You cannot dynamically overload an infix function.

Dynamic and overriding functions cannot be required by or supplied to modules.

If you export a dynamic function from a module you must also export all of its associated overriding functions.

You cannot use into to make an overriding function accept more than one type.

Overloading vs. hiding OmniMark keywords

It is important to understand the difference between overloading and hiding OmniMark keywords. OmniMark does not have reserved words. This means that you can define a variable or a function with the name of an OmniMark keyword. When you do this, the OmniMark keyword is hidden and will no longer be recognized as a keyword by OmniMark.

A specific set of OmniMark keywords are defined as being overloadable. This means that you can write an overloaded function that becomes part of the overloaded set of operations for that function name. For example, if you overload the + operator for use in adding points, it will still work for adding integers.

However, if you write a regular (non-overloaded) infix function with the symbol "+" as it's name, this will hide the built in + operator, which will then be unavailable.

In addition, if you write an overloaded function with the same name as an OmniMark keyword that is not overloadable, your overloaded function will hide the OmniMark keyword, and it will not be avaialble.

It is possible to access an hidden OmniMark keyword by preceding it with a backquote ` character. For instance, the following program (foolishly) hides the + character to make it perform concatenation. It then uses the backquote character to access the original + operator.

  define string infix-function
        value string a
     +
        value string b
  as
     return a || b
  
  
  process
     output "cat" + " mouse"
     output "d" % 2 `+ 4

Overloadable OmniMark keywords

The following OmniMark keywords are overloadable. You can write overloaded functions that become part of the overloading of these keywords.

These OmniMark keywords are overloadable infix functions:

These OmniMark keywords are also overloadable infix function. However, overloading of these functions must be defined to return a switch result.

  • =
  • !=
  • <
  • <=
  • >
  • >=

These OmniMark keywords are overloadable prefix functions: