|
|||||
|
|||||
Related Syntax | Related Concepts | ||||
declaration/definition |
define function |
Syntax
define result-type? function function-name argument-list? ((as function-body) | elsewhere)
You can define a function using define function
.
This is a simple example of a function definition:
define integer function add (value integer x, value integer y) as return x + y
A function must be defined before it can be called. That is, the definition of the function must occur in the source code before any calls to the function.
The function name follows the function
keyword and the data type, if specified. The function name must be a legal OmniMark name. The name of the function defined above is "add".
A function may return a value. If so, the data type of the return value is specified between the define
and function
keywords. The type may be any of the OmniMark data types or any user-defined record type, or any OMX component type.
You can call a function anywhere you can use an expression of the same type. For example, here the integer function "add" (defined above) is used as part of a numeric (integer) expression:
process local integer foo set foo to 27 - add (3,4)
A function may return a stream of input to the calling environment. An input function is like a stream returning function in that it returns a stream to the calling environment. However, an input function differs from a stream returning function in that its return value is composed of all the output generated while the function is executing (unless that output is specifically directed to another destination). The following input function scans a stream, escapes the markup characters, and returns the escaped stream:
define input function escape-text value stream original as repeat scan original match [\"&<>"]+ => ordinary output ordinary match "&" output "&" match "<" output "<" match ">" output ">" again
You can also define a function that does not return a value. Such a function is commonly called an "action function" because it is used just like an OmniMark action. To define an action function, simply omit the return type:
define function output-sum (value integer x, value integer y) as output "d" % x + y
You call an action function just as you would use a regular OmniMark action:
process output-sum(12,87)
While an action function does not return a value, it may modify the value of a shelf that is passed to it. The following function sets all the values of a switch shelf to false
.
define function flip (modifiable switch flags) as repeat over flags set flags to not flags again process local switch status initial{true,true,false,true} flip (status) repeat over status do when status = true output "true%n" else output "false%n" done again
You can use arguments to pass information to a function.
The declaration of a function argument has four parts:
The maximum number of arguments for a function call is 16383.
All OmniMark variables are shelves. OmniMark gives you four ways to pass a shelf as an argument to a function:
If an argument is declared modifiable
, the following conditions apply:
The following example uses a modifiable argument:
define function decrement-all (modifiable integer the-numbers ) as repeat over the-numbers decrement the-numbers again process local integer my-numbers size 5 initial {3,5,7,9,11} decrement-all(my-numbers) repeat over my-numbers output "%d(my-numbers)%n" again
write-only
arguments are chiefly useful when dealing with extended records.
If an argument is declared write-only
, the following conditions apply:
The following example uses a write-only argument. The function add-novel
has a write-only
argument of type "novel". However, the shelf passed to this function by the process
rule is of type "publication". This is allowed for a write-only
argument because "novel" is an extension of "book" which is an extension of "publication". This means that a record of type "novel" can be written to a shelf of type "publication". The write-only
argument type allows such a shelf to be passed to a function. Note also that the function cannot address the fields of objects on the shelf this-novel
because it does not know what types they are. (They might be books, or publications instead of novels.) To add an item to the shelf, therefore, the function declares a local record n
of type "novel", sets the values of its fields, and then assigns n
to the this-novel
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 binding field stream isbn declare record novel extends book field stream genre field stream locale variable define function add-novel (write-only novel this-novel) as local novel n set n:name to "The Wailing Wind" set n:publisher to "HarperCollins" set new n:author to "Tony Hillerman" set n:year-of-publication to "2002" set n:binding to "paperback" set n:isbn to "0061098795" set n:genre to "mystery" set new n:locale to "Four Corners" set new this-novel to n process local publication reading-material variable add-novel (reading-material)
If an argument is declared read-only
, the following conditions apply:
The following sample uses a read-only argument:
global stream animals size 3 initial {"dog", "cat", "cow"} define function output-all (read-only stream things-to-output ) as repeat over things-to-output output things-to-output when things-to-output is closed again process output-all (animals)
If an argument is declared value
, the following conditions apply:
The following sample uses value arguments:
global integer a initial {7} define integer function add (value integer x, value integer y) as return x + y process output "d" % add(2 + 6, add (a * 12, 9))
You can also pass a number of value arguments of the same type to a function using a "remainder" argument.
If an argument is declared remainder, the following conditions apply:
The following sample uses a remainder argument:
define function prefixed-output (value stream prefix, remainder stream items) as repeat over items output prefix || items again process prefixed-output ("<item>" , "red%n", "yellow%n", "blue%n")
When you use only a single remainder argument, you must include an ellipsis (three periods) in your function definition as shown below:
define integer function sum (remainder integer x, ...) as local integer a initial {0} repeat over x set a to a + x again return a process output "d" % sum (2,3,4,6)
You can declare an argument optional, which simply means that you do not need to specify a value for that argument when you call the function. To declare an argument optional, place the optional
keyword after the argument name.
If an argument is declared optional, the function must deal with the possibility that the argument value has not been specified. You can do this one of two ways.
value
, you can specify a default value for the argument.
is specified
.
The following code illustrates the use of initial
to specify a default value:
define stream function number-to-string (value integer the-number, value integer the-base optional initial {10} ) as local stream format-string set format-string to ("d" % the-base) || "rd" return format-string % the-number process local integer my-number initial {23456} output "Decimal: " || number-to-string (my-number ) || "%nHexadecimal: " || number-to-string (my-number , 16) || "%nOctal: " || number-to-string (my-number , 8) || "%nBinary: " || number-to-string (my-number , 2)
The following code illustrates the use of is specified
to test whether an optional parameter has been passed:
define stream function number-to-string (value integer the-number, value integer the-base optional ) as local stream format-string do when the-base is specified set format-string to ("d" % the-base) || "rd" else set format-string to "d" done return format-string % the-number process local integer foo initial {23456} output "Decimal: " || number-to-string (foo) || "%nHexadecimal: " || number-to-string (foo, 16) || "%nOctal: " || number-to-string (foo, 8) || "%nBinary: " || number-to-string (foo, 2)
The initial
value of optional
function arguments can be dynamic values. For example, the following function outputs a "modified" tag and dynamically calculates the default value of the "when" argument:
define input function modified-tag (value stream by, value stream why, value stream when optional initial {date "=xY-=xM-=xD =xH:=xm:=xs"}) as output "<modified by='" || by || "' why='" || why || "' when='" || when || "'>%c</modified>"
With the conventional parentheses-and-comma style of function arguments, you are only permitted one optional argument and it must be the last argument. The heralded form, discussed below, gives you greater flexibility.
You introduce the body of your function with the keyword as
. The body of the function consists of a sequence of OmniMark statements, just as in the body of a rule.
A function must be defined before it can be called. This is a problem if function A calls function B and function B calls function A. If A is defined before B, then A cannot legally call B. To get around this, you can predefine B before you define A. To predefine a function, simply replace as
with elsewhere
and omit the function body. Of course, you must still define B fully in its place, and the full definition must match all the elements of the predefinition.
So far, all the examples we have looked at use the conventional parentheses and commas for delimiting function arguments. The parentheses and commas are used in both the function declaration and the function call to separate one argument from another.
If you use the parentheses and commas form, it may not always be clear, when you read the resulting code, what role each of the function arguments plays.
You can make your code clearer if you use heralds instead of parentheses and commas to separate the arguments of your function. A herald is simply a word that you specify as a separator. Notice that the name of a function itself often acts as a herald, so the simplest form of a heralded function is one with a single argument and no parentheses:
define function output-twice value stream foo as output foo ||* 2 process output-twice "Tom"
If you have more than one argument, you can specify heralds as argument separators. Choose heralds that identify the role that the argument is to play. A well-chosen herald will make it easier to remember how to use your function, and easier to see what the function does in your code. A herald can be any token that follows the rules for OmniMark names:
define function output-repeatedly value stream foo repeats value integer repetitions as output foo ||* repetitions process output-repeatedly "Hip hip hooray!%n" repeats 3
While the function name itself is sometimes an adequate herald for the first argument, you will often want to specify a herald for the first argument (it is common and acceptable for the heralds and the argument names to be the same):
define integer function calculate-volume height value integer height width value integer width depth value integer depth as return height * width * depth
This is what the function call might look like:
set volume to calculate-volume height 12 width 7 depth 4
An important property of heralded function arguments is that they allow you to have more than one optional argument:
define stream function number-to-string value integer the-number using-base value integer the-base optional initial {10} width value integer the-width optional as local stream format-string do when the-width is specified set format-string to ("d" % the-base) || "r" || ("d" % the-width) || "fzd" else set format-string to ("d" % the-base) || "rd" done return format-string % the-number process local integer foo initial {23456} output "Decimal: " || number-to-string foo || "%nHexadecimal: " || number-to-string foo using-base 16 width 8 || "%nOctal: " || number-to-string foo using-base 8 || "%nWide: " || number-to-string foo width 12
Note that while it is legal to use the same herald for more than one argument of your function, you cannot have two consecutive arguments with the same herald if the first argument is optional, because it would be ambiguous as to which argument was intended by using the herald.
You can also use remainder arguments with heralded function names. In the case of a remainder argument with a herald, you must repeat the herald before each item in the function call.
define input function concatenate value stream a and remainder stream b as output a repeat over b output b again process output concatenate "This " and "dog " and "has " and "fleas."
When you call a function that uses conventional parentheses and commas to separate function arguments, you can pass a complex expression as a function argument. In the code below, the expression "6 * 5" is used as a function argument:
define integer function add (value integer x, value integer y) as return x + y process output "d" % add (4, 6 * 5)
If you use heralded function arguments, however, you must place complex expressions in parentheses:
define integer function add value integer x to value integer y as return x + y process output "d" % add 4 to (6 * 5)
If you omit the parentheses, OmniMark will recognize the first simple expression ("6" in this case) as the whole argument, and will raise a compile-time error when it sees the rest of the expression (unless, by chance, the rest of the expression constitutes valid OmniMark code in that place). This rule avoids the possibility of ambiguity in recognizing argument separators.