User-Defined Functions

This document covers the following topics:

Related topics:


Introduction to User-Defined Functions

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.

Restrictions

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.

Function Call versus Subprogram Call

The following is a comparison of the characteristics of a function call with those of a subprogram call.

What is similar?

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").

What is different?

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.

Example of a Function Call

The following example shows a program calling a function, and the function definition created with a DEFINE FUNCTION statement.

Program Calling the Function

** Example 'FUNCAX01': Calling a function  (Program)                    
************************************************************************
*                                                                       
WRITE 'Function call' F#ADD(< 2,3 >)  /* Function call.                   
                                      /* No temporary variables needed. 
*                                                                       
END 

Definition of Function F#ADD

** 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 

Example of a Subprogram Call

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.

Program Calling the Subprogram

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 

Called Subprogram FUNCAX04

** 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 

Function Definition (DEFINE FUNCTION)

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.

Symbolic and Variable Function Call

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.

Function Result and Parameters

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.

Explicit Prototype Definition (DEFINE PROTOTYPE)

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.

Implicit (Automatic) Prototype Definition

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.

Prototype Cast (PT Clause)

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.

Intermediate Result Definition (IR Clause)

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.

Combinations of Possible Prototype Definitions

In order to resolve a function call, the compiler needs information on

  • the function call mode (symbolic or variable);

  • 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.

Example with Multiple Definitions in a Function Call

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 

Evaluation Sequence of Functions in Statements

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.

Example:

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 

Using a Function as a Statement

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.

Example:

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