I'm not one to get rid of something that isn't broken, but I'm not sorry to see KLISTs go away.
Given the limitations of columnar RPG, the KLIST was a clever way to allow a programmer to specify a variable number of fields to access a keyed file, but at a price of a lot of lines of code for even simple jobs. Thankfully, free-format RPG has given us a way to do away with those many lines of code.
PLIST and KLIST: Alike but Not Identical
Like PLIST/PARM, the KLIST/KFLD keyword combination effectively creates a multiple-line statement allowing us to specify multiple arguments to the I/O opcodes, such as CHAIN and READE. However, something as simple as a loop with a partial key and a starting position requires multiple KLIST definitions, and as your access to the database becomes more complex and varied, so too do your KLISTs.
As I showed in my previous article, PLISTs have given way to the prototype. The prototype is far more than a simple replacement for the PARM/PLIST construct. Because it deals with so many different variations—as just one example, the same basic construct has to support calls to COBOL, CL, RPG, and Java—the prototype concept is syntactically complex.
The KLIST has a more-specific function: simply to provide access to a keyed file. It's no less important, but because the focus is on only that one thing—providing data to the underlying database support—it can take a more streamlined approach, as we'll see.
Simple KLIST Processing
Let's start with the simplest case. In this situation, we want to get all PO receipts for a specific item from a vendor. We assume a logical file, let's say PURRCTL1, by vendor and item. The KLIST definition shouldn't be too surprising:
C PURRCTK1 KLIST
C KFLD PRVEND
C KFLD PRITEM
Nothing out of the ordinary here. The only question is where the variables PRVEND and PRITEM are defined. In this case, we're going to take a page out of a software vendor's approach and use the actual database fields from the PURRCTL1 file to define the KLIST. Thanks to some reasonable naming conventions (I'm all about naming!), we can create a KLIST with a name that matches the logical file and we're in pretty good shape. The only trick now is to use that value.
I'm going to do a little programming gymnastics here and switch to free-format. You probably know that you can freely intersperse the two syntaxes, so it's not impossible to have free-format calculation specs that use a KLIST. Using a KLIST is pretty simple:
PRVEND = S1VEND;
PRITEM = S1ITEM;
setll PURRCTK1 PURRCTL1;
reade PURRCTK1 PURRCTL1;
dow not %eof(PURRCTL1);
addSubfile1();
reade PURRCTK1 PURRCTL1;
enddo;
It's not immediately obvious, but the fields S1VEND and S1ITNO are fields from a screen. Think of this as a subfile in which we want to show all the receipts for the vendor and item specified on the screen. The routine addSubfile1 formats the appropriate data and adds a line to the subfile. This is all quite simple and shows the strengths of the KLIST syntax.
When KLISTs Go Wrong
It's not really wrong, just a bit cumbersome. As well-meaning as the KLIST/KFLD concept may be, it can quickly go off the rails for any sort of complex scenarios. Let's watch how that happens by adding just a tiny complication. In this new case, we want to be able to set our position within the list of receipts using a starting date. To do that, we need a new KLIST.
C PURRCTK1S KLIST
C KFLD PRVEND
C KFLD PRITEM
C KFLD PRDATE
Why the second KLIST? Well, because we need to position within the vendor and item. You've probably done this many times, but let's take a look at the code:
PRVEND = S1VEND;
PRITEM = S1ITEM;
PRDATE = S1DATE;
setll PURRCTK1S PURRCTL1;
reade PURRCTK1 PURRCTL1;
dow not %eof(PURRCTL1);
addSubfile1();
reade PURRCTK1 PURRCTL1;
enddo;
You'll notice one new line of code and one change. First, the new line initializes the date field. Second, we use the second KLIST for the SETLL opcode. This means we don't position to the first record for that vendor and item, but to the first one for that date or later. However, the READE opcode remains the same because we still want to include only the records for that vendor and item.
So now we have two KLISTs, and a lot of lines of code to set the key values. What if we added a second complication? Let's say we want to do something similar but from a second panel. The code looks like this:
PRVEND = S2VEND;
PRITEM = S2ITEM;
PRDATE = S2DATE;
setll PURRCTK1S PURRCTL1;
reade PURRCTK1 PURRCTL1;
dow not %eof(PURRCTL1);
addSubfile2();
reade PURRCTK1 PURRCTL1;
enddo;
You can see how the code begins to accumulate. Add code for different views and it really begins to multiply quickly, and you end up with a non-executable part of your program that may contain hundreds of lines of code just defining your KLISTs. Worse yet, you need lots of lines of code to initialize your variables, and those lines of code can be far away from the actual I/O opcodes. This leads to bugs when someone adds code that has a side effect of changing the variables in the KLIST.
Free Yourself from KLISTs!
The free-format version of file I/O lets you do away with KLISTs completely. There are actually two different syntactical approaches. One technique uses something called a key data structure and the %KDS built-in function, but I don't use it. I built a small application using just %KDS one time to see how it works. This approach can provide some advantages, especially in environments with very strict naming conventions (and you know how much I love naming conventions!). But in the end, %KDS simply replaces the KLIST with a data structure. And while the data structure is based on the file itself and so tends to be more future-proof than just using work variables in a KLIST, I don't think the advantages are sufficient to justify the approach.
Instead, I prefer a much more localized technique. Specifically, I like to use a key expression right on my opcodes. Here's the code for both loops using the inline approach:
setll ( S1VEND: S1ITEM: S1DATE) PURRCTL1;
reade ( S1VEND: S1ITEM) PURRCTL1;
dow not %eof(PURRCTL1);
addSubfile1();
reade ( S1VEND: S1ITEM) PURRCTL1;
enddo;
setll ( S2VEND: S2ITEM: S1DATE) PURRCTL1;
reade ( S2VEND: S2ITEM) PURRCTL1;
dow not %eof(PURRCTL1);
addSubfile2();
reade ( S2VEND: S2ITEM) PURRCTL1;
enddo;
That's all the code. There is no need for additional non-executable code, nor do you need any work variables. I particularly like the fact that you can see exactly which fields you are using for the operation; there's no worry about using variables that might have been changed at some point.
The inline approach has other benefits, which we'll touch upon in later articles. One of my favorite advantages is that you can use expressions right in the code. Let's assume for a moment that the third field in the logical file is a CCYYMMDD date field (yes, I know we prefer dates, but I'll bet you have one or two numeric date fields in your legacy database!). You could easily do something like this:
setll ( S1VEND: S1ITEM: %dec(%date – 3 %months:*iso)) PURRCTL1;
This syntax will always set the cursor to start three months earlier than today's date. I love it.
So today we learned about the inline key expression in free-format opcodes. You probably also noticed that I used the standard I/O opcode built-in function %eof instead of an indicator. That actually was sort of mandatory, since indicators aren't even supported in the free-format I/O opcodes, but I think they make the code look better, too! But indicators are another area where we can leverage free-format syntax—and more specifically, we can use free-format to get rid of indicators entirely. But that's for another article!
LATEST COMMENTS
MC Press Online