This document covers the following topics:
Related topics:
Natural object type function
Natural statements DEFINE
FUNCTION
, DEFINE
PROTOTYPE
A user-defined function is a programming object of type function, containing Natural statements which implement a specific functional task. The invocation of a user-defined function ("function call") usually has a number of parameters and returns a result.
The syntactical representation of a function call is the function name (call-name
) followed by a special bracket notation containing the parameters, which is, for example: FCTNAME(<...>)
.
A function can be called from any place within a Natural statement where an operand is expected which is only read, but not modified. The result returned by the function is processed by the statement at the place where the function call is embedded, like a field containing the same value.
A function can also be called in a stand-alone mode, outside of any Natural statement. In this case, the function result is not processed.
Usually, the result value returned by the function depends on the parameters provided with the function call. As well as with
a Natural subprogram, a parameter can be defined within a function as a "by reference", "by value" or "by value result" field. This makes it possible to provide parameter values in a function call which are only transfer compatible to what is
defined in the function definition. Moreover, it allows you to exchange data between the calling object and the function not
only via the function result, but also via parameters. The correctness of the parameter list and the compatibility of the
result value is checked at compilation, either by means of an existing cataloged function object or by means of a result and
parameter layout definition described with a DEFINE PROTOTYPE
statement.
All function calls used in a Natural statement are evaluated in a separate step before the statement execution is started. They are evaluated in the same order in which they appear in the statement.
At some places in a programming object, function calls cannot be used. This includes
positions where the operand value is changed by the Natural statement;
all kinds of database calls (for example, FIND
, READ
);
DEFINE DATA
statement;
IF BREAK
statement;
AT BREAK
statement;
array index expressions;
Natural system functions (for example, AVER
, SUM
);
parameters of a function call.
The following is a comparison of the characteristics of a function call with those of a subprogram call.
The following similarities exist between a function call and a subprogram call:
The programming code forming the routine logic is coded inside a separate object, either in a function or a subprogram.
Parameters are defined in the object using a DEFINE DATA PARAMETER
statement, with various communication modes (for example, "by value").
The following differences exist between a function call and a subprogram call:
A function call can be used at any position in a Natural statement where a read-only operand is possible (for exceptions,
see Restrictions), whereas subprograms can be invoked only via the CALLNAT
statement.
A function returns a result value which can instantly be processed by the statement that includes the function call. The use
of a temporary variable is not required. A CALLNAT
statement can only return data via its parameters. To process such a value with another statement, it needs to be declared
as an explicit variable.
Parameters and the result of a function call are always verified if the called function already exists at compilation time.
Subprogram calls are checked only if the compiler option PCHECK
is set to ON
.
The name of a function, which is used to call the function, is defined within the DEFINE
FUNCTION
statement and may differ from the name of the object containing the function. Similar to subroutines, the name of a function
may have up to 32 characters. A subprogram is called by the name of the subprogram object. Therefore, the maximum name length
is limited to 8 characters.
The following example shows a program calling a function, and the function definition created with a DEFINE
FUNCTION
statement.
** Example 'FUNCAX01': Calling a function (Program) ************************************************************************ * WRITE 'Function call' F#ADD(< 2,3 >) /* Function call. /* No temporary variables needed. * END
** Example 'FUNCAX02': Calling a function (Function) ************************************************************************ DEFINE FUNCTION F#ADD RETURNS #RESULT (I4) DEFINE DATA PARAMETER 1 #SUMMAND1 (I4) BY VALUE 1 #SUMMAND2 (I4) BY VALUE END-DEFINE /* #RESULT := #SUMMAND1 + #SUMMAND2 /* END-FUNCTION * END
To implement the same functionality as shown in the example of a function call by using a subprogram call instead, you need to specify temporary variables.
The following example shows a program calling a subprogram, involving the use of a temporary variable.
** Example 'FUNCAX03': Calling a subprogram (Program) ************************************************************************ DEFINE DATA LOCAL 1 #RESULT (I4) INIT <0> END-DEFINE * CALLNAT 'FUNCAX04' #RESULT 2 3 /* Result is stored in #RESULT. * WRITE '=' #RESULT /* Print out the result of the /* subprogram. * END
** Example 'FUNCAX04': Calling a subprogram (Subprogram) ************************************************************************ DEFINE DATA PARAMETER 1 #RESULT (I4) BY VALUE RESULT 1 #SUMMAND1 (I4) BY VALUE 1 #SUMMAND2 (I4) BY VALUE END-DEFINE * #RESULT := #SUMMAND1 + #SUMMAND2 * END
A function is created in a separate Natural object of type function. It contains a single DEFINE
FUNCTION
statement, which defines the parameter interface, the result value and the program code forming the operation logic.
There are two modes to call a function, either in a direct form or indirect form. In the direct form (denoted as "symbolic" function call), the function name specified in the call is exactly the name of the function itself. In the indirect form (denoted as "variable" function call), the name specified in the function call is an alphanumeric variable with any name, which contains the name of the called function at runtime.
To define a variable function call, it is always necessary to use a DEFINE PROTOTYPE VARIABLE
statement. Otherwise, the function call is assumed to be a symbolic function call; this means, the name itself is regarded
as the function name.
See Function Call for more details about this topic.
Usually, function calls are used within Natural statements instead of variables or constant values. Since the compiler strictly requires the layout of the operands involved, it is essential to get the format, length and array structure of a function result. Moreover, if the parameter structure of a function is known, the parameters supplied in a function call can be checked for correctness at compilation time.
There are three options to provide this information
with a DEFINE
PROTOTYPE
statement;
implicit by the existent object (cataloged version) of the called function, which is automatically loaded by the compiler
if no DEFINE PROTOTYPE
statement was found before;
with an explicit (IR=
) clause specified in the function call.
The simplest way is certainly to use the second option, as it always takes the information from the object which is called
at runtime. However, this is only possible if the cataloged object of the called function is available. If a function is called
with a variable function call, via a variable containing the function name, the name of the function object is unknown and
as a consequence cannot be identified and loaded. In this case a DEFINE PROTOTYPE
statement can be used to describe the function interface. The first two options comprise a full validation of the result
and the parameters.
If neither a DEFINE PROTOTYPE
statement nor the existent object of a function call is available, two casting options can be used in a function call. These
are
(IR=
) to specify the function result format/length/array structure. This option does not incorporate any parameter checks.
(PT=)
to use a previously defined prototype with a name other than the function name.
To specify the interface of a certain function, a DEFINE
PROTOTYPE
statement can be used. This statement defines the layout of the parameters, which are to be passed in a function call, and
the format/length of the result field returned by the function. Furthermore, it indicates whether a function call is a "symbolic" or a "variable" function call.
When a function call was found, Natural tries to locate a prototype with the same name as the used function name. If such a prototype is found, the function result and parameter layouts of this prototype are used to resolve the function call. Especially, the decision on the function call mode ("symbolic" or "variable") is made at this place.
If a function call is resolved in a program, the compiler searches for a DEFINE
PROTOTYPE
statement with the function name, which has been defined before. If such a statement cannot be found, Natural tries to load
the called function into the buffer pool. If successful, the function layout of the result and and the parameters is extracted
from the object data and kept as if it was provided by an explicit DEFINE PROTOTYPE
statement for this function. This manner of use is denoted as automatic prototype definition. It assures great conformity between the interface definition (at compile time) and the passing accuracy at runtime.
In order to get the interface layout of a called function, Natural tries to locate a DEFINE
PROTOTYPE
statement with the same name as the function identifier. If such a statement is not available, and the called function object
cannot be loaded (see Implicit (Automatic) Prototype Definition
), a (PT=)
clause can be specified in the function call. If such a clause is applied, the DEFINE PROTOTYPE
statement with the referenced name (which is different from the function name) is used to describe the function result and
to validate the parameters.
Example:
#I := #MULT(<(PT=#ADD) 2 , 3>)
In this example, the function #MULT
is called, but the result and parameter layouts are used from a prototype whose name is #ADD
.
Usually, the function result is specified by a DEFINE
PROTOTYPE
statement, which is either coded explicitly or will be created automatically with the function object (see Implicit (Automatic) Prototype Definition
). If such a definition is not available, the result layout can be specified by using the (IR=
) clause in the function call. If this clause is used, it determines which format/length the compiler should use for the result
field in the statement generation. This clause can also be specified if the prototype definition is available for a function
call. In this case, the result layout in the prototype is overruled by the (IR=)
clause specification; the parameter checks, however, are performed according to the prototype definition.
In order to resolve a function call, the compiler needs information on
the layout (format/length) of the function result;
the layout (format/length) of the function parameters.
Different options allow you to provide this data, which are the explicit prototype definition, the implicit prototype definition,
the (PT=)
option, and the (IR=
) option. But which one has an effect if multiple of these clauses are used?
A function call is used as a variable function call if there is a related prototype with the same name, which contains a VARIABLE
clause. In all other cases, the function call is treated as a symbolic function call.
The result is determined in the following order:
the definition provided in (IR=
), if this clause is specified;
the RETURNS
definition in the prototype referenced in (PT=)
, if this clause is specified;
the explicit prototype definition (DEFINE PROTOTYPE
) with the same name as used in the function call, if it exists;
the implicit prototype definition, which is loaded automatically from the existing function object.
If none of these options applies, a syntax error is raised.
The parameter checks are performed according to the definition in:
the prototype definition referenced in (PT=)
, if this clause is specified;
the explicit prototype definition (DEFINE
PROTOTYPE
) with the same name as used in the function call, if it exists;
the implicit prototype definition, which is loaded automatically from the existing function object.
If none of these options applies, the parameter validation is not performed. This allows you to supply any number and layout of parameters in the function call, without receiving a syntax error.
Program:
** Example 'FUNCBX01': Declare result value and parameters (Program) ************************************************************************ * DEFINE DATA LOCAL 1 #PROTO-NAME (A20) 1 #PARM1 (I4) 1 #PARM2 (I4) END-DEFINE * DEFINE PROTOTYPE VARIABLE #PROTO-NAME RETURNS (I4) DEFINE DATA PARAMETER 1 #P1 (I4) BY VALUE OPTIONAL 1 #P2 (I4) BY VALUE END-DEFINE END-PROTOTYPE * #PROTO-NAME := 'F#MULTI' #PARM1 := 3 #PARM2 := 5 * WRITE #PROTO-NAME(<#PARM1, #PARM2>) WRITE #PROTO-NAME(<1X ,5>) * WRITE F#MULTI(<(PT=#PROTO-NAME) #PARM1,#PARM2>) * WRITE F#MULTI(<(IR=N20) #PARM1, #PARM2>) * END
Function F#MULTI
:
** Example 'FUNCBX02': Declare result value and parameters (Function) ************************************************************************ DEFINE FUNCTION F#MULTI RETURNS #RESULT (I4) BY VALUE DEFINE DATA PARAMETER 1 #FACTOR1 (I4) BY VALUE OPTIONAL 1 #FACTOR2 (I4) BY VALUE END-DEFINE /* IF #FACTOR1 SPECIFIED #RESULT := #FACTOR1 * #FACTOR2 ELSE #RESULT := #FACTOR2 * 10 END-IF /* END-FUNCTION * END
Instead of operands, function calls can be used directly in statements. However, this is only allowed with operands which are only read, but not modified by the statement.
All function calls are evaluated before the statement execution starts. The returned result values are stored in temporary fields and passed to the statement. The functions are executed in the same order in which they appear in the statement. If a function call has parameters which are modified by the function execution, you should consider that this can influence the statement result. This may apply if the same parameter is used at another place in the same statement.
Before the COMPUTE
statement is started, variable #I
has the value 1
. In a first step, function F#RETURN
is executed. This changes #I
to value 2
and returns the same value as the function result. After this, the COMPUTE
operation starts and adds the incremented #I (2)
and the temporary field (2)
to a sum of 4
.
Program:
** Example 'FUNCCX01': Parameter changed within function (Program) ************************************************************************ DEFINE DATA LOCAL 1 #I (I2) INIT <1> 1 #RESULT (I2) END-DEFINE * COMPUTE #RESULT := #I + F#RETURN(<#I>) /* First evaluate function call, /* then execute the addition. * WRITE '#I :' #I / '#RESULT:' #RESULT * END
Function:
** Example 'FUNCCX02': Parameter changed within function (Function) ************************************************************************ DEFINE FUNCTION F#RETURN RETURNS #RESULT (I2) BY VALUE DEFINE DATA PARAMETER 1 #PARM1 (I2) BY VALUE RESULT END-DEFINE /* #PARM1 := #PARM1 + 1 /* Increment parameter. #RESULT := #PARM1 /* Set result value. /* END-FUNCTION * END
Output of Program FUNCCX01
:
#I : 2 #RESULT: 4
A function can also be called stand-alone, without being embedded in other statements. In this case, the function return value is completely ignored.
If such an execution mode is desired, only the function call is coded, which then stands for a statement. In order to prevent an unwanted link to the previous statement in the source code, a semicolon must be used to explicitly separate the function call from this statement.
Program:
** Example 'FUNCDX01': Using a function as a statement (Program) ************************************************************************ DEFINE DATA LOCAL 1 #A (I4) INIT <1> 1 #B (I4) INIT <2> END-DEFINE * * WRITE 'Write:' #A #B F#PRINT-ADD(< 2,3 >) /* Function call belongs to operand list /* immediately preceding it. * WRITE // '*************************' // * WRITE 'Write:' #A #B; /* Semicolon separates operands and function. F#PRINT-ADD(< 2,3 >) /* Function call does not belong to the /* operand list. * END
Function:
** Example 'FUNCDX02': Using a function as a statement (Function) ************************************************************************ DEFINE FUNCTION F#PRINT-ADD RETURNS (I4) DEFINE DATA PARAMETER 1 #SUMMAND1 (I4) BY VALUE 1 #SUMMAND2 (I4) BY VALUE END-DEFINE /* F#PRINT-ADD := #SUMMAND1 + #SUMMAND2 /* Result of function call. WRITE 'Function call:' F#PRINT-ADD /* END-FUNCTION * END
Output of Program FUNCDX01
:
Function call: 5 Write: 1 2 5 ************************* Write: 1 2 Function call: 5