Let's Build a Procedure

RPG
Typography
  • Smaller Small Medium Big Bigger
  • Default Helvetica Segoe Georgia Times

Procedures (or "subprocedures," as are they are called in RPG IV) are the fundamental building blocks of all RPG IV applications. If you are not regularly using procedures in your RPG IV code, you are simply writing RPG III programs using RPG IV syntax. Being an RPG III programmer in RPG IV clothing isn't a bad thing to be, but you need to take that next step toward writing true RPG IV code.

To get to that point, you need to start using procedures. Procedures are a bit different from RPG's traditional subroutines in that they are more than just a repeated section of code. Procedures are not a replacement for subroutines; instead, they are a different kind of technology whose capability overlaps the capability of subroutines. In other words, you can continue to write subroutines, but there is absolutely no compelling reason to do so. Everything you do with subroutines can be accomplished with procedures.

From a high-level perspective, procedures differ from subroutines as follows:

  • Procedures accept parameters just like programs do but with more optional features. Subroutines do not accept parameters.
  • Procedures can be used as an extension to the RPG IV language. In other words, you can create a procedure, such as TOUPPER, whose purpose in life is to convert lowercase to uppercase, and then use that procedure within a conditional statement or on an assignment statement. You can't do that with subroutines.
  • You can declare fields within procedures, and those fields will be isolated to the procedure. This means that outside of the procedures, those fields do not exist; they are not known to the other procedures or mainline calcs in the program. Therefore, you can use more meaningful names for work fields and other variables when they are declared within a procedure. (And that means you can avoid using symbols such as #, @, and $ in field names!)
  • Procedures can be compiled and stored separately from the program itself. You can actually create a source member that contains nothing but a few of your favorite procedures, compile it, and then link to it at compile time, just as if the procedures were embedded in the main program itself. Subroutines, however, must always be declared in the source of the main program.

These few points are just the beginning of the advantages of procedures over subroutines.

Building That First Procedure

Probably the most difficult step in using procedures is creating that first working procedure. To do this, you need a couple of new statements in RPG IV that declare the procedure. Those statements are the P specifications.

The P specifications declare the start and end points of a procedure. You need two P specifications for each procedure you create: one to indicate the start of the procedure code and one to indicate the end of the procedure code.

Think of the P specification of a procedure as the equivalent of the BEGSR and ENDSR opcodes. For example, to declare a subroutine named TOUPPER, you might have the following specified:

     CSR   ToUpper       Begsr

      **  subroutine code goes here

     CSR   endToUpper    Endsr

To declare a procedure named TOUPPER, you would have the following code with P specifications instead of BEGSR and ENDSR statements:

     P ToUpper         B

        //  procedure code goes here

     P ToUpper         E

OK, the difficult part is over. Now, you need to migrate your subroutine-writing skills to procedure-writing skills. To do this, you first need to have an example subroutine that you want to migrate to a procedure. Illustrated below is the example RPG IV source code that uses work fields and a subroutine to convert lowercase to uppercase. Granted, I could have just call the XLATE opcode and avoided the call to the subroutine altogether, but I needed something to illustrate this example.

D UPPER           C                   'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
D lower           C                   'abcdefghijklmnopqrstuvwxyz'
D TU_Work         S           1024A   Varying 

D work1           S           1024A   Varying
D work2           S           1024A   Varying

C     CustNo        Chain     CustRec
C                   If        %Found()  
C                   move      CSTNAME       TU_WORK
C                   exsr      ToUpper
C                   move      TU_WORK       Work1

C                   move      OLDNAME       TU_Work
C                   exsr      ToUpper
C                   move      TU_WORK       Work2
C                   if        Work1 = Work2
 **  Old/New names are identical...
C                   endif
C                   endif

CSR   ToUpper       Begsr
C
C     lower:UPPER   XLate     TU_Work       TU_Work
     
CSR   endToUpper    Endsr

In this example, a customer record is retrieved from the customer master file, and the CSTNAME field from that file is going to be compared to the previous value for that field. This allows you to verify whether or not the name of the customer has changed yet ignore the upper/lowercase differences between the two fields.

Now, rewrite this using RPG IV syntax. First, create just the procedure itself, without changing any of the mainline calc's logic. Here is a fundamental version of the TOUPPER subroutine, written as a procedure.

P ToUpper         B
D ToUpper         PI

C     lower:UPPER   XLate     TU_Work       TU_Work
C                   return

P ToUpper         E

You need to add three lines of code to the TOUPPER procedure to make it a functioning component. The D specification that immediately follows the P specification is used to identify the procedure equivalent of the *ENTRY PLIST. In other words, this declares the parameter list for the procedure. This is known as the "procedure interface." So remember, the term "procedure interface" means "the procedure's *ENTRY PLIST."

Since you're not using parameters at this point, you specify an empty procedure interface.

Now, you need to go into your existing code and change it so that the procedure is called instead of the subroutine. There are three ways to call a procedure, which may be why some people get confused (too many options), but they're easy to get used to. These are the three ways to call a procedure:

  • Using the CALLB opcode
  • Using the CALLP opcode
  • Implicitly, by specifying the procedure within an expression

For now, you simply want to have a plug-in replacement for the subroutine call, so replace the call to the TOUPPER subroutine with a CALLB to the TOUPPER procedure. Here's the completed code:

C     CustNo        Chain     CustRec
C                   If        %Found()  
C                   move      CSTNAME       TU_WORK
C                   CALLB     'TOUPPER'
C                   move      TU_WORK       Work1

C                   move      OLDNAME       TU_Work
C                   CALLB     'TOUPPER'
C                   move      TU_WORK       Work2
C                   if        Work1 = Work2
 **  Old/New names are identical...
C                   endif
C                   endif

In the example above, the EXSR statements have been replaced with CALLB statements. Note that, in RPG IV, the compiler converts procedure names to uppercase by default. Therefore, when you call a procedure, the procedure name must be in uppercase; otherwise, the compiler will not locate it. If you had specified the CALLB statement like the following, the compile would have failed:

C                   CALLB     'ToUpper'

Now you've created a procedure that can replace the old subroutine, but so what? What advantage does this procedure have over the subroutine call? The answer to this question is simply maintainability.

People say 80% of all programming is maintenance programming. Making changes means working with existing code. If you can reduce the number of lines of code that have to be fiddled around with and isolate those lines of code, you can increase programmer productivity.

For example, suppose you want to avoid using the work fields in these examples. You can easily do that with the TOUPPER procedure without concern for the field names of any other fields (work fields or otherwise) in the program source.

Let's look at a slightly different version of the TOUPPER procedure. This one avoids the use of work fields and instead takes advantage of the parameter capability of procedures.

0001 P ToUpper         B
0002 D ToUpper         PI          1024A   Varying 
0003 D  input                      1024A   Varying CONST

0004 D UPPER           C                   'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
0005 D lower           c                   'abcdefghijklmnopqrstuvwxyz'
0006 D output          S           1024A   Varying
     
0007 C     lower:UPPER   XLate     Input         Output
0008 C                   return    Output
0009 P ToUpper         E

In this updated version of the TOUPPER procedure, I've made the following changes:

  • Added a parameter named INPUT
  • Added a field named OUTPUT
  • Specified a return value for the procedure
  • Moved the named constants UPPER and LOWER to the procedure
  • Returned the OUTPUT field to the caller

To add parameters to procedures, you simply specify the parameter name, its type, and its length, which is virtually identical to declaring a subfield of a data structure. In fact, using procedure parameter lists is very similar to declaring data structures.

In addition, you can specify that the parameter is not going to be changed by the procedure; to do this, add the CONST keyword to the parameter's description (see line 3). Making procedure parameters CONST helps to improve performance when calling the procedure; of course, the downside is that you cannot change the value of CONST parameters from within the procedure.

Return Value

Procedures, like programs, allow you to modify the value of any parameter passed into them (provided, as I mentioned, that the parameter does not contain the CONST keyword). This works essentially the same way that passing parameters to programs works.

Procedures have one more capability, however. Procedures can return a value to the caller without going through a parameter variable. This capability classifies the procedure as a function instead of a procedure, but in RPG IV, we use the term "procedure" generically to mean both procedure and function.

When a procedure is a function, its return value may be used within an expression (a conditional statement or an EVAL operation). For example, since the TOUPPER procedure returns a value, you can use it as follows:

C                   eval      Work1 = TOUPPER(CSTNAME)

Or, better yet, like this:

C                   if        TOUPPER(CSTNAME) = TOUPPER(OLDNAME)

This method not only greatly simplifies the code, but also isolates the original 10-line routine into a single statement.

Let's look at how this TOUPPER procedure would be called compared to the TOUPPER subroutine by rewriting the original code as follows:

C     CustNo        Chain     CustRec
C                   If        %Found()  
C                   if        TOUPPER(CSTNAME) = TOUPPER(OLDNAME)
 **  Old/New names are identical...
C                   endif
C                   endif

This is much cleaner and easier to read and maintain. A maintenance programmer will appreciate all the clutter of those work fields being eliminated.

One Final Component

Procedures, while easier to maintain than other methods do have one confusing element, prototypes. Prototypes are the single biggest source of confusion for RPG IV programmers when they attempt to move to procedures.

Simply put, prototypes are required when using procedures. The compiler uses the prototype source code to syntax check calls made to the procedure to make sure the number of parameters and their data types exactly match what the procedure is expecting.

So in our example, if you specified TOUPPER(12345), you would get a compile-time error message. Try that with a subroutine!

The easiest way to use prototypes is to just accept the fact that the compiler requires them and create them by copying the procedure interface code.

To create a prototype, simply copy the entry parameter list for your procedure, including the PI line and all parameter fields, to another location within the source member. Then, go to the letters "PI" in columns 24 and 25 of that PI line and change them to "PR" (meaning prototype).

Here is the prototype source for the TOUPPER procedure:

0002 D ToUpper         PR          1024A   Varying
0003 D  input                      1024A   Varying CONST

From the source statement sequence numbers, you should be able to tell that I simply copied the procedure interface code from the procedure definition to create this prototype. I then changed the letters "PI" (procedure interface) to "PR" (prototype). This source code must be at the top of the source member with the regular D specifications (above the mainline calcs).

For completeness, I want to illustrate the entire original subroutine method rewritten for procedures:

0001 H DFTACTGRP(*NO) 
0002 FCUSTMAST  UF   E           K DISK    PREFIX('CM.')
0003 FCUSTEDIT  CF   E             WORKSTN

0004 D ToUpper         PR          1024A   Varying
0005 D  input                      1024A   Varying CONST

0006 D CM            E DS                  Extname(CUSTMAST) QUALIFIED
0007 D OLDNAME         S                   Like(CM.CSTNAME) 

0008 C     CustNo        Chain     CustRec
0009 C                   If        %Found()  
0010 C                   if        ToUpper(CM.CSTNAME) = ToUpper(OLDNAME)
      **  Old/New names are identical...
0011 C                   endif
0012 C                   endif


0013 P ToUpper         B
0014 D ToUpper         PI          1024A   Varying
0015 D  input                      1024A   Varying CONST

0016 D UPPER           C                   'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
0017 D lower           c                   'abcdefghijklmnopqrstuvwxyz'
0018 D output          S           1024A   Varying 
     
0019 C     lower:UPPER   XLate     Input         Output
0020 C                   return    Output
0021 P ToUpper         E

In the example above, the field CM.CSTNAME from the customer master file is compared with the field OLDNAME (line 10). The TOUPPER procedure is called twice in one statement, and its return value is what is being compared. Since this procedure uses variable-length fields, the original value's length is also the length of the data that is returned by the procedure.

Just Do It

Procedures are different; they look different and are coded differently. But they are not and should not be scary. Nor should they be avoided. The use of procedures will lead you down a path of easier program maintenance and much greater flexibility than previously available. While you do not need to convert existing subroutines into procedures, you should start writing all new maintained code using procedures whenever the situation calls for a subroutine.

Bob Cozzi is a programmer/consultant, writer/author, and software developer. His popular RPG xTools add-on subprocedure library for RPG IV is fast becoming a standard with RPG developers. His book The Modern RPG Language has been the most widely used RPG programming book for more than a decade. He, along with others, speaks at and produces the highly popular RPG World conference for RPG programmers.

BLOG COMMENTS POWERED BY DISQUS