Guru: Fall Brings New RPG Features, Part 2
January 11, 2021 Jon Paris
In my previous tip I outlined some of the new features added to RPG with the Fall 2020 release. In this and the following tip I will be covering the features that I ran out of space for in that first one.
This time I will cover the new FOR-EACH loop construct. I have wanted this in RPG ever since encountering it in PHP. Simply put, it automatically iterates through an array, “serving up” one element at a time. When I say “an array” I mean any kind of array, including data structure arrays, dynamic arrays and even the new temporary arrays created by the %LIST BIF that I mentioned in the previous tip.
Here’s a really simple example of using FOR-EACH to process all of the elements in a simple standalone array:
dcl-s nameArray char(10) Dim(100); dcl-s name like(nameArray); // The old way For element = 1 to %elem(nameArray); Dsply nameArray(element); EndFor; // The For-Each way For-Each name in nameArray; Dsply name; EndFor;
Notice that because FOR-EACH extracts each element into the name field, we don’t need to use a subscript to access the element within the loop. Not that big a deal when processing a simple array, but it makes life a lot simpler when dealing with a multi-field DS array. You’ll see an example of this in a moment.
Where FOR-EACH really shines, though, is when working with the new dynamically sized arrays. Because RPG knows how many active elements are in the array, it is able to use that information to control the operation of the loop.
One of my favorite uses of the new dynamic array support is for handling SQL result sets, so I’ll use SQL for this next example. What I love is that the combination of the dynamic array and FOR-EACH means that I don’t actually have to use the SQLERRD(3) value to control the loop. Here’s my simple example.
Dcl-Ds results ExtName('ANIMALS') qualified dim( *Auto : 1000 ) End-ds; Dcl-Ds animal likeDs(results); Dcl-s type like(results.TYPE); Dsply ( 'What type of animal do you want?' ) ' ' type; Exec SQL Declare animalsCursor cursor for Select * from ANIMALS where type = :type; Exec SQL Open animalsCursor; Exec SQL Fetch from animalsCursor for 1000 rows into :results; Dsply ( 'Found ' + %Char( %Elem( results ) ) + ' matching animals' ); For-Each animal in results; Dsply ( 'Id: ' + %Char(animal.Id) + ' - Name: ' + animal.name ); EndFor;
You can also see here what I meant by simplified field references. Had I coded the loop manually, references such as animal.Id and animal.name would have needed to be coded as results(element).Id and results(element).name. More typing and less elegant.
For me though, this is not just a reduction in typing. It also increases the readability in the sense that the intent of my code is more obvious, i.e., I am going to process every active element in this array.
Now that you’ve seen how much easier this combination can make processing SQL just imagine how much better still it would be if we didn’t have to worry about having to use a cursor. Wouldn’t it be nice just to do a multi-row SELECT into the target DS? That would be so much easier and closer to what happens in other languages. It just so happens that I am not the only one who feels this way, and there is an RFE out there requesting that IBM provide this functionality. IBM do pay a lot of attention to the number of votes that requests like this receive so, if you agree with me, won’t you join me in voting for this enhancement? You’ll find it at https://www.ibm.com/developerworks/rfe/execute?use_case=viewRfe&CR_ID=141665.
Is FOR-EACH A Good Fit For All Your Array-Processing Needs?
As you have seen, FOR-EACH works beautifully when processing all the elements of an array and in particular, when processing a dynamic array. But what about a traditional (i.e., fixed-sized) RPG array that has not been filled? Prior to the advent of FOR-EACH we would normally code this in a regular FOR loop, using the highest active element number as the upper limit. We can use FOR-EACH in such situations, but it requires that we use %SUBARR to limit the scope of the array and the resulting code is not quite so elegant. As a result, I suspect I may still use the old approach in such situations, but take a look at the comparison example below and form your own opinion.
// Traditional FOR loop approach For element = 1 to nameCount; Dsply nameArray(element); EndFor; // Using FOR-EACH with a partially filled array For-each name in %Subarr(nameArray: 1: nameCount); Dsply name; Endfor;
In my third and final tip on these enhancements, I will be taking a look at a couple of new compiler options designed to aid in conversions from character to numeric. You’ll see that not only do they improve the operation of BIFs such as %DEC, but also aid in processing XML and JSON.
Jon Paris is one of the world’s foremost experts on programming on the IBM i platform. A frequent author, forum contributor, and speaker at User Groups and technical conferences around the world, he is also an IBM Champion and a partner at Partner400 and System i Developer. He hosts the RPG & DB2 Summit with partners Susan Gantner and Paul Tuohy.