7.92.4 SUBROUTINE Examples - Part 2
Techniques for documenting SUBROUTINES using the BBUSE template
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
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