Guru: Procedure Driven RPG With Linear-Main Programs
September 11, 2023 Gregory Simmons
A number of years ago, I started migrating away from writing subroutines and started writing procedures instead. Yes, quite often, this was simply because it was “new and shiny” and served no real benefit from their subroutine counterpart. However, as the language and I evolved, I found that my method of approaching every project was what I call procedure driven RPG.
Let’s have a look at a simple RPG program. In this little program, to give it a purpose, I’m going to have a little fun with math and demonstrate the Fibonacci sequence:
1 **Free 2 Dcl-s i Uns(3) Inz(3); 3 Dcl-s fib1 Uns(3) Inz(0); 4 Dcl-s fib2 Uns(3) Inz(1); 5 Dcl-s fib3 Uns(3) Inz; 6 Exsr Perform_Fibonacci_Sequence; 7 *InLr = *On; 8 Return; 9 Begsr Perform_Fibonacci_Sequence; 10 Dsply ('Fibonacci Sequence:'); 11 For i = 3 to 10; 12 fib3 = fib1 + fib2; 13 fib1 = fib2; 14 fib2 = fib3; 15 Dsply ('' + %Char(fib1)); 16 Enddo; 17 Endsr;
Line 1: **Free – This small demo is written in full free RPG.
Line 2-5: Some global variables that I will use during the subroutine. I initialize count to 3 because the first two sequences (0 and 1) are not interesting to me.
Line 3: Execute the subroutine.
Line 7-8: This is the traditional cycle RPG program, so I turn on LR and return.
Line 9-17: This subroutine does a simple Do While loop to display the current number then calculate the next number in the sequence.
So, if I compile and run this program, then display my joblog, I can see the sequences of:
Now, let’s have a look at this same program and simply switch from a cycle RPG program to a linear main RPG program.
1 **FREE 2 Ctl-Opt Main(Perform_Fibonacci_Sequence); 3 Dcl-Proc Perform_Fibonacci_Sequence; 4 Dcl-s i Uns(10) Inz; 5 Dcl-s fib1 Uns(10) Inz(1); 6 Dcl-s fib2 Uns(10) Inz(1); 7 Dcl-s fib3 Uns(10) Inz; 8 dsply ('Fibonacci Sequence:'); 9 For i = 3 to 10; 10 fib3 = fib1 + fib2; 11 fib1 = fib2; 12 fib2 = fib3; 13 Dsply ('' + %Char(fib1)); 14 Endfor; 15 End-Proc Perform_Fibonacci_Sequence;
Line 1: This program will continue to be in fully free format.
Line 2: To make this a linear main program, I add the control option with the Main keyword to name the main procedure.
Line 3: I can immediately dive right into my procedure to perform the Fibonacci sequence.
Lines 4-7: These are the same variables used in the Fibonacci loop. Notice that I have chosen to define them locally now. If I know that a variable will be used by multiple procedures, I sometimes will define them starting with ‘gbl_’. But I find that more often than not, I prefer to just pass variables between whichever procedures need those values. I think I may have some mental scars from days of debugging old RPG programs with hundreds, if not thousands of global variables. Subroutines changing their values as they need to, display files also using them . . . and then they were poorly named . . . and then there’s a ton of gotos . . . aahhhh . . . .
Lines 8-14: This is the same logic to calculate / demonstrate the Fibonacci sequence.
In this tiny program, the RPG compiler will not embed the RPG cycle. Notice that I no longer need to set on *Inlr. As a linear main RPG program, while it does initialize variables, open any files, and lock data areas, it does not use the *Inlr to trigger a shutdown. Additionally, resources that are opened at start up are not implicitly closed when the program ends. In this program, the performance boost attributed to moving from cycle RPG program to a linear main RPG program is negligible. However, as program complexity increases this can play a significant role in performance.
I ran each version of the above program in batch and caused them to delay so I had time to view the call stack inside the subroutine in the first one and then inside the procedure in the second version. Note the differences:
The first program with the subroutine just tells you that I’m running the program Fibonacci, while the second program lets me know that I’m running the Perform_Fibonacci_Sequence procedure within the program Fibonacci3. If I am investigating a bug, that pinpoint accuracy is valuable!
My fellow developers and I am no longer debug programs that “Eat the whole elephant,” as we say. We are usually just reviewing a small procedure whose sole purpose is to take just one bite. If your shop has a generic logging program like ours does, then logging whenever something unexpected happens allows us to have a quick look at the logs and know exactly which program, service program, or procedure had the problem. Again, we are quickly down to the small procedure that requires our attention.
Now, by my count, migrating this program from a cycle RPG program to a linear main RPG program also reduced the number of lines of code from 17 to 14 . . . Hmm, can we do it in even fewer lines? I’ll give it a go:
1 **Free 2 Ctl-Opt Main(Perform_Fibonacci_Sequence); 3 Dcl-Proc Perform_Fibonacci_Sequence; 4 Dcl-s i Uns(10); 5 Dcl-s fib Uns(10) Dim(10); 6 Dsply ('Fibonacci Sequence:'); 7 fib(2) = 1; 8 For i = 3 to %Elem(fib); 9 fib(i) = fib(i-1) + fib(i-2); 10 Dsply ('' + %Char(fib(i))); 11 Endfor; 12 End-Proc Perform_Fibonacci_Sequence;
By using an array, I can reduce my number of variables from 4 to 2. And then my math is simpler as well. That gets it down to 12 lines from 14. I suppose I could get it down to 10 if I left out the display functions. Ah well, a fun little exercise.
As of late, I’ve been learning how to shift my mindset from that of a developer to that of a project manager. This is where I feel procedure driven RPG really packs a punch. How do you eat an elephant? One bite at a time, right? So, for my project to manage, I’ve been given a colossal project. As I define tasks for the developers assigned to assist me with this project, I whittle each business function down to a unit of work. And each unit of work is? You guessed it: a procedure.
That procedure may be in a program or it may be in a module which is exported in a service program. But each of these procedures has one specific job to perform. And while some of these procedures could be absorbed into the calling procedure since it’s only called by one procedure, we have already started seeing cases where we need to perform a certain task and realize ‘Hey! We already have a procedure that does that!’ Situations like this, where we find that the wheel has already been invented, equals time saved. And for those CTOs, CIOs, and IT managers, that equals money saved.
Until next time, happy coding.
Gregory Simmons is a software engineer with PC Richard & Son. He started on the IBM i platform in 1994, graduated with a degree in Computer Information Systems in 1997 and has been working on the OS/400 and IBM i platform ever since. He has been a registered instructor with the IBM Academic Initiative since 2007, and holds a COMMON Application Developer certification. When he’s not trying to figure out how to speed up legacy programs, he enjoys speaking at COMMON and other technical conferences, running, backpacking, SCUBA diving, hunting, and fishing.
Nice summary! Thanks for walking through the process!
I thought the **free wasn’t needed anymore?? It does compile without that. Or is that what tells the compiler that we aren’t using the logic cycle?
I understand it’s just an exercise, but implementing the array to save 2 lines of code makes it much more difficult for the next person to decipher. fib1/fib2/fib3 are much more descriptive and easily understood at a glance.