Built-in functions (BIFs) allow you to significantly reduce the maintenance code in programs; here's an example.
Built-in functions (BIFs) are designed to make programmers more productive. They do this in a number of ways, but one my favorites is how they reduce the number of lines of code in my programs. If you've used BIFs you're probably already familiar with how they simplify your code; for example, date manipulation is far simpler using the %date BIFs. Today, though, I'm going to show you something that might surprise even the BIF experts out there. I know it surprised me!
BIFs in I/O
I/O operations have been enhanced greatly in RPG and especially in RPG /free. A couple of the real time-savers are the %fields extension to update and write operations and the key field list in the SETxx and chain operations. Today's article will focus on the key field list because it supports an additional enhancement that can remove even more code!
Let's start by examining the free-format I/O operations and specifically the key field list. A quick progression through the various syntactical variants is in order. Let's start old school with some fixed-format RPG/400:
TAXKEY CHAIN TAXFILE 90
I think most people will recognize this syntax. The KLIST opcode heads a list of key fields, which are then used in the CHAIN operation. In this case, field one is the customer number from the order header and field two is the item type from the item master. Together, these two fields provide a key to the tax override file. So we create a key list using those two fields and chain. The problem with this syntax is that the key list and the actual I/O operation are usually separated in the code, especially if you use the key list in more than one I/O operation. This means you have to page up and down through the code in order to understand how the logic works.
One way around this particular problem is to use work fields. This is something we do all the time; we create a KLIST with fields specific to that KLIST. An interesting quirk of fixed-format syntax is that you can actually define the fields right in the KLIST. This allows you to define work fields, which you can then load prior to the I/O operation. Here's an example:
KFLD XFKEY1 6 0
KFLD XFKEY2 1
MOVE OHCUST XFKEY1
MOVE IMTYPE XFKEY2
TAXKEY CHAIN TAXFILE 90
This approach has benefits and drawbacks. The primary disadvantage of using work fields is that you end up with more work fields! Work fields are inherently bad because each one creates a potential failure point. For example, if a field in the database changes, you can end up with a size mismatch between it and the work field. A more subtle problem is the concept of a side effect; for various reasons, we sometimes initialize one or more of the key fields in another part of the program. For example, we might move OHCUST into XFKEY1 as soon as we retrieve the order header record. That's fine, unless some other part of the program changes XFKEY1. If that happens, you'll start seeing unexpected and difficult-to-debug errors.
The free-form variant of the CHAIN operation has a syntax that resolves both of these issues:
chain ( OHCUST: IMTYPE) TAXFILE;
How about that? You can specify the key fields right on the CHAIN instruction itself. I find this to be extremely helpful; not only do I not need any work variables, but the fields used to retrieve the record are right there on the line. This is the sort of "self-documenting" code that really helps you down the road when it's time to maintain this program. This syntax has an extremely useful feature: as long as the fields are "compatible," the compiler will perform conversions as necessary to get the operation to execute. One example might be converting packed to zoned, or casting a smaller alpha field to a larger one, or even using a constant in the CHAIN. Generally speaking, if the compiler can do the conversion on a procedure call, then it can do it on an I/O key list.
But wait! There's more!
Let's imagine a situation where the data can't be auto-converted. For example, let's say that in this case the customer number is numeric in the order header file but alphanumeric in the tax file. Normally, this would require a little magic involving the %editc BIF. In fact, this is the code I've used in similar circumstances:
wCust = %editc(OHCUST:'X');
chain ( OHCUST: IMTYPE) TAXFILE;
It works, but it requires a work field. Another option is to use the field in the TAXFILE record itself. That's also functional, and it avoids the work field. But as I continued to explore the language, I found out I was doing extra work (and I hate doing extra work!). The field list actually supports expressions, which means I can put the conversion right in the CHAIN operation. Here is the true least-effort solution:
chain ( %editc(OHCUST:'X'): IMTYPE) TAXFILE;
Now that's a real time-saver! The field list handler will actually execute the %editc BIF and use the result in the I/O operation. That to me is some serious magic. I am able to do conversions using any of the opcodes or BIFs at my disposal. If the third character of my item number is the type code, I can substring it out right on the line. I can build a complex lot number using a date field and a concatenation. And if I extend the concept to its logical conclusion, I can use the result of a subprocedure, even one in a service program! Imagine this:
chain ( %editc(OHCUST:'X'): getTaxType( IMITEM) ) TAXFILE;
The getTaxType routine can be a procedure in a service program that does any business logic I need to determine the tax type. Now I'm no longer limited to just storing the tax type in the item master. Of course, in this particular case I might be better off just using a service program procedure to get the tax type and avoid the CHAIN altogether, but I hope you can see the potential of the approach. Many more subtle but powerful syntactical techniques exist in RPG; this is not the Report Program Generator I grew up with in the '70s. I'll show you more in another article!