The new Technology Refreshes, IBM i 7.4 TR3 and 7.3 TR9, has given us a couple of new additions to array handling in the RPG language.
The first is a Built in Function, %LIST, that allows me to fill an array in one statement, and introduced me to a concept I had not heard of in the RPG language before, a temporary array.
The other is an operation code, FOR_EACH, that allows me to read an entire array, one element at a time in a loop operation without having to condition the loop with the number of elements in the array.
Let me start with %LIST Built in Function.
Having played with the %LIST BiF I have found it can perform two tasks:
- Load an existing array
- Create a temporary array
This code snippet shows how I loaded an array using this BiF.
01 dcl-s Array char(2) dim(5) ; 02 Array = %list('A' : 'B' : 'C' : '' : 'E') ; 03 Array = %list(' 1' : ' 2' : ' 3' : ' 4' : ' 5': ' 6') ; |
Line 1: This is the definition for my array. It is 2 characters with 5 elements.
Line 2: I think is so cool. I can move the values into the array all in one line! There is no letter D as I wanted to see what happened if I used the equivalent of null, two apostrophes next to one another with nothing inbetween. Using debug looking at the contents of the array after this line shows:
> EVAL array ARRAY(1) = 'A ' ARRAY(2) = 'B ' ARRAY(3) = 'C ' ARRAY(4) = ' ' ARRAY(5) = 'E ' |
The fourth element contains blanks, rather than null.
Line 3: I want to overlay the contents of the array with these values. But wait I have six values to move into an array with only five elements. What will happen?
Why do I have a leading blank before the numbers?
When I used just "1" the program would not compile, giving me the following message:
*RNF0531 20 1 The length of the indicator item is not 1. |
The compiler has interpreted the "1" as the same as "*ON", this is valid for an indicator. It has not interpreted it as a character value.
Displaying, in debug, the contents of the array after line 3 shows me that the sixth value in the list was dropped.
> EVAL array ARRAY(1) = ' 1' ARRAY(2) = ' 2' ARRAY(3) = ' 3' ARRAY(4) = ' 4' ARRAY(5) = ' 5' |
What does the documentation mean by a "temporary array"? From what I can gather from the documentation a temporary array is used like a VALUES clause in SQL. I have a list of values, and I can try to match a value to it. For example:
01 if ('RED' in %list('RED' : 'GREEN' : 'BLUE')) ; 02 dsply 'Success' ; 03 else ; 04 dsply 'Failure' ; 05 endif ; |
Line 1: This If statement checks if the value "RED" is in the temporary array defined by the %LIST BiF. Notice that the new operation code IN is needed.
Line 2: As "RED" is in the temporary array this line is executed.
And I see:
DSPLY Success |
I have to use the IN operation code as I cannot use the %LOOKUP BiF I would with a "permanent" array. The documentation also informs me that I cannot use the SORTA operation code or the %SUBARR BiF with temporary arrays.
Rather than use a value, or constant, as the string to look for I can use a variable.
01 dcl-s SomeColor varchar(25) ; 02 SomeColor = 'PURPLE' ; 03 CheckColor() ; 04 SomeColor = 'BLUE' ; 05 CheckColor() ; 10 dcl-proc CheckColor ; 11 if (SomeColor in %list('RED' : 'GREEN' : 'BLUE')) ; 12 dsply (%trimr(SomeColor) + ' found') ; 13 else ; 14 dsply (%trimr(SomeColor) + ' not found') ; 15 endif ; 16 end-proc ; |
Line 1: I have defined my variable as variable length character.
Lines 2 and 3: I move the word "PURPLE" to the variable then I call the subprocedure CheckColor.
Lines 10 to 16: In the subprocedure the string within the variable to "looked" up in the temporary array. "PURPLE" is not found and the display operation code is used to show the result:
DSPLY PURPLE not found |
Lines 4 and 5: The word "BLUE" is moved to the variable, and the subprocedure is called.
As blue is in the temporary array the following is displayed.
DSPLY BLUE found |
Can I use variables as the elements in the temporary array?
01 dcl-s Var1 char(25) inz('ANNE') ; 02 dcl-s Var2 like(Var1) inz('BETTY') ; 03 dcl-s Var3 like(Var1) inz('CATHY') ; 04 dcl-s TestVar like(Var1) ; 05 TestVar = 'ANNE' ; 06 if (TestVar in %list(Var1 : Var2 : Var3)) ; 07 dsply (%trimr(TestVar) + ' successful') ; 08 else ; 09 dsply (%trimr(TestVar) + ' failed') ; 10 endif ; |
Line 6: The temporary array contains the variables defined in lines 1 – 3. I have initialized them with values so I do not have to move values to them later in the program.
Line 7: As the value "ANNE" is in the temporary array I send a message using the display operation code.
DSPLY ANNE successful |
The new operation code, FOR-EACH, allows me to read all the elements in an array. If I wanted to use a FOR group I would have:
01 dcl-s Element packed(2) ; 02 for Element = 1 to %elem(Array) ; 03 dsply Array(Element) ; 04 endfor ; |
Line 2: I need to know how many elements there are in the array to make that the maximum value for the For operation. This I provide using the %ELEM BiF.
Line 3: And I need to give which element I want to, in this case, display.
The new FOR-EACH simplifies this.
01 dcl-s SomeValue char(10) ; 02 for-each SomeValue in Array ; 03 dsply SomeValue ; 04 endfor ; |
Line 2: No need to give a starting or ending range as the operation starts with the first element of the array and continues until it reaches the end. It moves the value for each element, in turn, to the variable SomeValue.
Line 3: I am displaying the contents of SomeValue, which shows how this loops through the entire array.
DSPLY 1 DSPLY 2 DSPLY 3 DSPLY 4 DSPLY 5 |
I can use this with a temporary array too.
01 for-each SomeValue in %list('AUSTIN' : 'DALLAS' : 'HOUSTON') ; 02 dsply SomeValue ; 03 endfor ; |
Which shows:
DSPLY AUSTIN DSPLY DALLAS DSPLY HOUSTON |
I do wonder if I would ever use this operation for a temporary array.
You can learn more about this from the IBM website:
This article was written for IBM i 7.4 TR3 and 7.3 TR9.
I would say that the RNF0531 error you received is an error in the compiler. The value '1' is a valid value for the character array.
ReplyDeleteHi Simon,
ReplyDeletethanks for this article, I cant’t wait to test it.
Unfortunately, my compiler doesn’t like neither "%List" nor "in". However, our admin says he has applied 7.3 TR9.
I checked if PTF MF99209 is installed with the help of another great article (https://www.rpgpgm.com/2014/12/quick-way-to-find-if-ptf-present-and.html).
It is "permanently applied", but still CRTRPGMOD results in RNF0604 errors (invalid token) when %List is in my source.
WRKPTFGRP results in
SF99727 Level 9 installed
What’s going wrong?
Best regards,
Markus
The PTFs for RPG are not always included in the TR group PTF. I would get your Admin to double check that he has downloaded and applied the latest RPG PTFs.
DeleteThanks a lot, Simon.
ReplyDeleteAs to using %List in a for-each loop, I’d maybe use something like this (because on 7.3 I can’t use varying length arrays yet afaik):
// Merging the final PDF Document
arrAllpages =%List(FRONTCOVER:TITLEPAGE:CONTENTPAGE:REGISTER:BACKCOVER);
if chrUserchoice = WHITELABEL;
for-each page in %List(WHITEPAGE:CONTENTPAGE);
addPage(page);
endfor;
elseif chrUserchoice = STANDARD;
for-each page in %List(TITLEPAGE:CONTENTPAGE:REGISTER);
addPage(page);
endfor;
elseif chrUserchoice = FULL;
for i = 1 to %Elem(arrAllpages);
addPage(arrAllpages(i));
endfor;
endif;
Okay, I admit, it’s not really beautiful because of the repeating for loops, probably an extra procedure would be the better choice.
Best regards,
Markus
You can still use the FOR-EACH construct in your last case:
Deleteelseif chrUserchoice = FULL;
for-each page in arrAllpages;
addPage(page);
endfor;
endif;
One thing to keep in mind as you use for-each is this: you are using copies of the values from the array. If you change the value of an array element inside the loop, the change does not apply to the array elements, only the copy.
ReplyDeleteBeen using this since it came out. Loving it! %range and %list are the bomb!
ReplyDeleteI didn't expect that I'd use those so often. They absolutely can improve readability.
ReplyDeleteIt's a little bit annoying that NOT IN doesnʼt work.
Markus, if you want to code "IF X NOT IN Y", code like this: "IF NOT (X IN Y)"
ReplyDeleteif not (SomeColor in %list('RED' : 'GREEN' : 'BLUE'));
As usual, you saved my day... thanks.
DeleteThanks a lot for this article.
ReplyDelete%list and %range were long overdue. These have been standard function in other languages for a long time.
ReplyDelete