7.121.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.121.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 of Number(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)

EndroutineEnd_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