7 92 4 SUBROUTINE Examples Part 2

LANSA Technical

7.92.4 SUBROUTINE Examples - Part 2

Techniques for documenting SUBROUTINES using the BBUSE template

Recursion

Subroutine variables are not locally scoped

Emulating local scoping by using a naming standard

Techniques for saving and restoring globally scoped variables

7.92.3 SUBROUTINE Examples - Part 1

Techniques for documenting SUBROUTINES using the BBUSE template

When using the SUBROUTINE command it is often a good idea to use the LANSA template (for both Visual LANSA and LANSA for IBM i) called BBSUB.

This template will provide the basic coding layout for subroutine like this:

*=============================================================================
*Subroutine ....:
*Description....:
*=============================================================================
SUBROUTINE NAME(SUB1)
ENDROUTINE
 

If a parameter is required for the subroutine then this template will provide the basic coding layout automatically like this:

*=============================================================================
*Subroutine ....:
*Description....:
*Parameters ....:   Name  Type  Len  Description
*----- ------- ------- -------- -------------
*#XXXXXX    XXX  99,9 XXXXXXXXXXXXXXXXXXXX
    *=============================================================================
SUBROUTINE NAME(SUB1) PARMS(#XXXX)
ENDROUTINE 
 

For example subroutine named SUB1 has parameters #EMPNO, #SURNAME and #GIVENAME, these parameters can then be commented in the basic layout created by the BBUSE template to make the subroutine more understandable and easier to implement in the future.

Hence the layout can contain information about fields used as parameters used in the subroutine like this:

*=============================================================================
*Subroutine....:SUB1
*Description....: To retrieve an employee record from file PSLMST
*Parameters ....: #EMPNO, #SURNAME and #GIVENAME  
*Name        Type      Len      Description       
*-----       -------   -------  -----------------------------
*#EMPNO1     A         5        Employee number
*#NAME1      A         20       Surname
*#NAME2      A         20       Givename 
    *==========================================================================
SUBROUTINE
NAME(SUB1) PARMS((#EMPNO1 *RETURNED) (#SURNAME *RETURNED) (#GIVENAME *RETURNED))
ENDROUTINE
 

Recursion

You should avoid recursively invoking SUBROUTINEs, either directly or indirectly.

Here SUBROUTINE SUB_A is invoked recursively by itself:

SUBROUTINE SUB_A
<< ETC >>
EXECUTE SUB_A
<< ETC >>
ENDROUTINE  
 

Within Visual LANSA, this example will produce a fatal error, while in LANSA for i, it simply will fail to compile.

Here SUBROUTINE SUB_A is invoked recursively by SUBROUTINE SUB_B

SUBROUTINE SUB_A
<< ETC >>
EXECUTE SUB_B
<< ETC >>
ENDROUTINE  
 
SUBROUTINE SUB_B
<< ETC >>
EXECUTE SUB_A
<< ETC >>
ENDROUTINE  
 

Once again within "Visual LANSA", this example will produce a fatal error, but in LANSA for i, it will end up in a recursive loop which will have to be ended manually.

Unlike subroutines MTHROUTINES (Method routines) in RDMLX are allowed to be recursive, so this factorial calculator should function correctly:

Mthroutine Factorial
Define_Map *input #Std_Num #OfNumber
Define_Map *output #Std_Num #ReturnResult
If        '#OfNumber.Value = 1'
Set       #ReturnResult Value(1)
Else      
Change    #Std_Num '#OfNumber.Value - 1'
Invoke    #Com_Owner.Factorial OfNumber(#Std_Num) ReturnResult(#ReturnResult)
Change    #Std_NumL '#OfNumber.Value * #ReturnResult.Value'
Set       #ReturnResult Value(#Std_Num)
Endif     
Endroutine
 

So Invoke #Com_Owner.factorial ofNumber(4) ReturnResult(#Std_Num) should return a result of 4 * 3 * 2 *1 = 24.

Subroutine variables are not locally scoped

Often the arguments received and returned by subroutines are defined within the subroutine like this:

SUBROUTINE NAME(A) PARMS(#A #B #C)
DEFINE    FIELD(#A) REFFLD(#SALARY)
DEFINE    FIELD(#B) REFFLD(#PERCENT)
DEFINE    FIELD(#C) REFFLD(#SALARY)
 

In RDML, such field definitions are simply a convention and are not locally scoped. The fields are globally scoped within the RDML function (i.e: accessible to all the code in the function).

So in this example in this code, SUBROUTINE SUB_A will return the value 42.45, not 17.72:

FUNCTION  OPTIONS(*DIRECT)
Define    Field(#newsal) Reffld(#salary)
Execute   Subroutine(SUB_A) With_Parms(#newsal)
Display   Fields(#newsal)
         
Subroutine SUB_A ((#A *returned))
Define    #A reffld(#salary)
Change    #A 17.72
Execute   SUB_B
Endroutine 
         
Subroutine SUB_B
Change     #A 42.45
Endroutine
 

This happens because #A is globally scoped. So when you reference #A in your code it is always the same instance of #A.    

In RDMLX though, the EVTROUTINEs, MTHROUTINEs and PTYROUTINEs do support local scoping.

If you coded the previous SUB_A and SUB_B subroutines as methods like this:

Function  Options(*Direct)
Begin_Com Role(*Extends #Prim_Form)

Define_Com 
Class(#Salary.Visual) Name(#Salary) DisplayPosition(1) Height(19) Left(43) Parent(#Com_Owner) TabPosition(1) Top(72) Width(278)
         

Evtroutine handling(#com_owner.Initialize)
Set       #com_owner caption(*component_desc)

Invoke    
#com_owner.SUB_A A(#Salary)
Endroutine 
         

Mthroutine
 SUB_A
Define_Map *output #Salary #A  
Set       #A Value(17.72)
Invoke    #com_owner.SUB_B
Endroutine 
         
Mthroutine SUB_B
Define_Com Class(#Salary) Name(#A)  
Set       #A Value(42.45)
Endroutine
End_Com

 

The method SUB_A would return 17.72.

This happens because there are two locally scoped #As defined.

One in method SUB_A and another method SUB_B.

If however you defined your code like this:

Define_Com Class(#Salary) Name(#A)
         

Mthroutine
 SUB_A
Define_Map *output #Salary #ReturnValue  
Set       #A Value(17.72)
Invoke    #com_owner.SUB_B
Set       #ReturnValue Value(#A)
Endroutine 
         
Mthroutine SUB_B
Set       #A Value(42.45)
Endroutine
 

Then method SUB_A would again return 42.45, because #A is a globally scoped component, so SUB_A and SUB_B are both referring to the same #A.

Emulating local scoping by using a naming standard

Even though locally scoped variables are not supported in subroutines you can (if required) emulate them by using a simple naming standard.

For example, each subroutine ensures that its arguments and variables are uniquely defined:

FUNCTION  OPTIONS(*DIRECT)
DEFINE    FIELD(#PERCENT) TYPE(*DEC) LENGTH(4) DECIMALS(1) DESC(PERCENTAGE) EDIT_CODE(3)
REQUEST   FIELDS(#EMPNO #PERCENT)
FETCH     FIELDS(#SALARY) FROM_FILE(PSLMST) WITH_KEY(#EMPNO)
EXECUTE   SUBROUTINE(SUB_A) WITH_PARMS(#SALARY #PERCENT #EMPNO)
*         
SUBROUTINE NAME(SUB_A) PARMS((#A_001 *Received)(#B_001 *received)(#C_001 *Received))
DEFINE    FIELD(#A_001) REFFLD(#SALARY)
DEFINE    FIELD(#B_001) REFFLD(#PERCENT)
DEFINE    FIELD(#C_001) REFFLD(#EMPNO)
CHANGE    FIELD(#A_001) TO('#A_001 * #B_001')
DISPLAY   FIELDS(#C_001 #A_001)
EXECUTE   SUB_B (#A_001 #B_001 #C_001) 
ENDROUTINE 
*         
SUBROUTINE NAME(SUB_B) PARMS((#A_002 *Received)(#B_002 *received)(#C_002 *Received))
DEFINE    FIELD(#A_002) REFFLD(#SALARY)
DEFINE    FIELD(#B_002) REFFLD(#PERCENT)
DEFINE    FIELD(#C_002) REFFLD(#EMPNO)
CHANGE    FIELD(#A_002) TO('#A_002 - 500')
DISPLAY   FIELDS(#C_002 #A_002)
EXECUTE   SUBROUTINE(SUB_C) WITH_PARMS(#A_002 #C_002)
ENDROUTINE 
*         
SUBROUTINE NAME(SUB_C) PARMS((#A_003 *received)(#C_003 *Received))
DEFINE    FIELD(#A_003) REFFLD(#PERCENT)
DEFINE    FIELD(#C_003) REFFLD(#EMPNO)
CHANGE    FIELD(#A_003) TO('#A_003 - 100')
DISPLAY   FIELDS(#EMPNO #A_003)
ENDROUTINE
 

If you code Execute SUB_A (#Salary #Percent #Empno), you are sure that these values being passed between the various subroutines will not inadvertently interfere with the globally scoped values of #Salary #Percent #Empno

Techniques for saving and restoring globally scoped variables

The previous example provides a simple technique for emulating locally scoped variables.

Sometimes when you are writing a subroutine you cannot avoid overwriting a globally scoped variable (e.g.: you have to fetch a field from a file).

Equally when you are maintaining a subroutine you cannot always be sure what other use is already being made of globally scoped variables elsewhere in the program without a detailed examination. 

In these situations people typically use a simple save/restore technique to ensure that the globally scoped variable(s) remain unchanged by the execution of the subroutine.

For example, imagine you had to construct subroutine named SUB_A that needed to fetch the department (#DEPTMENT) in which an employee worked in its logic: 

Subroutine Name(SUB_A) Parms((#A_001 *received))
Define    #A_001 Reffld(#Empno)  
*<<etc>>   
Fetch     (#Deptment) from_file(pslmst) with_key(#A_001)
*<< etc >> 
Endroutine
 

Now imagine that field #DEPTMENT was already used in several places in the program.

To ensure that you are not upsetting the value of field #DEPTMENT in your new subroutine you would probably code this:

Subroutine Name(SUB_A) Parms((#A_001 *received))
Define    #A_001 Reffld(#Empno)  
Define    #Save_001 Reffld(#Deptment)  
*<< etc >> 
Change    #Save_001 #Deptment
*<< etc >> 
Fetch     (#Deptment) from_file(pslmst) with_key(#A_001)
*<< etc >> 
Change    #Deptment #Save_001
*<< etc >> 
Endroutine
 

This ensures that the value of #DEPTMENT is unchanged by the execution of your subroutine.

Now imagine that you also have to reference #SECTION, #SURNAME and #STARTDTE.

Your subroutine now looks like this:

Subroutine Name(SUB_A) Parms((#A_001 *received))
Define    #A_001 Reffld(#Empno)  
Define    #SavA_001 Reffld(#Deptment)  
Define    #SavB_001 Reffld(#Section)  
Define    #SavC_001 Reffld(#Surname)  
Define    #SavD_001 Reffld(#Startdte)  
*<< etc >> 
Change    #SavA_001 #Deptment
Change    #SavB_001 #Section
Change    #SavC_001 #Surname
Change    #SavD_001 #Startdte
*<< etc >> 
Fetch     (#Deptment) from_file(pslmst) with_key(#A_001)
*<< etc >> 
Change    #Deptment #SavA_001
Change    #Section #SavB_001 
Change    #Surname #SavC_001 
Change    #Startdte #SavD_001 
*<< etc >> 
Endroutine
 

However, by using a simple working list you can achieve the same result in a more efficient, easier to read and more maintainable manner:

Subroutine Name(SUB_A) Parms((#A_001 *received))
Define    #A_001 Reffld(#Empno) 
 

Global fields that may have been overwritten by this subroutine

Def_List  #Save_001 (#Deptment #Section #Surname #StartDte) Type(*Working) Entrys(1)
 

Save the value of all globally defined fields that may be overwritten

Inz_List  #Save_001 Num_Entrys(1)
*<< etc >> 
Fetch     (#Deptment #Section #StartDte #Surname) from_file(pslmst) with_key(#A_001)
*<< etc >>
 

Restore the value of all globally defined fields that may have been overwritten

Get_Entry 1 #Save_001
Endroutine