26
Fri, Apr
1 New Articles

Prototypes: For All Calls, All the Time

RPG
Typography
  • Smaller Small Medium Big Bigger
  • Default Helvetica Segoe Georgia Times
Utilizing the full capabilities of prototypes can make your life as a programmer easier and make your programs more bulletproof.

 

With the use of free-format (aka /Free) RPG logic becoming more and more popular, many shops are beginning to utilize prototypes for their program calls because the CALL operation code is one of those that doesn't work in free-form. This is a movement to which I say, "It's about time!" For far too long, many shops have used prototypes only with subprocedures. Even with this upswing in the use of prototypes, however, I've found that many programmers are still not taking advantage of all that prototypes have to offer. Too many shops do the bare minimum necessary to get their program calls to function in /Free and are not leveraging all that this great language feature offers. Prototyping has been with us for a long time, since V3R2 in fact. So in this article, let's take a look at some of the under-utilized features of prototypes.

Prototype Basics

First, let's look at the basics of prototyped calls for those who haven't yet made the switch. Let's compare a program call with CALL to one with CALLP. Of course, we can call subprocedures with prototypes as well, but people using subprocedures are already familiar with the use of prototypes for that, so we'll concentrate here on using prototypes for program calls.

 

Take a look at the following code that calls a program to calculate payroll taxes:

  

   C                   Eval      PayAmt = GrossPay + Bonus

   C                   Call      'PA0132'

   C                   Parm                    PayAmt

   C                   Parm                    Allow

   C                   Parm                    FedTax

   C                   Parm                    StateTax

                                                      

To call this program with a CALLP operation, I could create a prototype and use a CALLP in place of the CALL, like the following example:

 

   D CalcTaxes       PR                  ExtPgm('PA0132')

   D  TotalPay                      7P 2  

   D  Allow_Nbr                     3S 0

   D  FedTaxOutput                  5P 2

   D  StTaxOutput                   5P 2

   D PayAmt          S              7P 2

   

   C                   Eval      PayAmt = GrossPay + Bonus

   C                   CallP     CalcTaxes(PayAmt : Allow : FedTax : StateTax)

 

If my reasons for using a prototype were to use /Free logic, the CALLP statement would look more like this:

 

      PayAmt = GrossPay + Bonus;  

      CalcTaxes(PayAmt : Allow : FedTax : StateTax);

 

For those of you not familiar with prototypes, notice that the prototype is defined in the D specs. The syntax is similar to that of a data structure in that the D specs that follow the prototype beginning statement (indicated by the PR) that have the "PR" columns blank are part of the prototype. They represent the parameters. The definition of the PayAmt standalone field indicates the end of the parameter descriptions for the program. 

 

One thing that may look odd to you if you haven't used prototypes before is the fact that the "field names" used in the prototype do not match the field names passed as parameters on the CALLP statement. I put "field names" in quotes because the text in the field name position in a prototyped parameter definition is not naming a field at all. It is merely used as documentation for you and your fellow programmers to make it easier to tell the parameters apart. The data type and size definitions (as well as any keywords, which we will discuss later) are the critical parts of the parameter definitions.

 

Note that the EXTPGM keyword is required when calling a program, even if you decide to name the prototype the same as the *PGM object you are calling. I'm a big believer in making logic as understandable as possible, however, so I like to take advantage of the ability to override the (usually less meaningful) *PGM object name to a name that describes its function, as I did in this example (i.e., changing PA0132 to CalcTaxes). The CALLP statement now looks a bit more like a call to a built-in function, with the parameters in parentheses after the prototype name.

 

Besides being able to write the operation in free-form and using a more meaningful name in the logic, are there any other benefits to using the prototoyped call in the example above? The biggest benefit is the fact that the number of parameters and the data type and size of each parameter used on the call will be verified at compile time against those in the prototype. In the example of the CALL ... PARM statements before, if the Allow field in this calling program had been defined as 5-digit packed decimal with 2 decimals and the called program expected it to be zoned decimal 3-digit with no decimals, it would not have been detected until run time (hopefully during testing!). Likewise, if only three parameters had been passed when four were expected, it would cause a run-time error. In either of these cases, when using a prototype, the errors would be detected by the compiler. It's obviously much easier and faster to identify and fix a compile-time error than a run-time error.  

 

Of course, for that compile-time check of parameter types to be completely effective, the programmer must ensure that the prototype definition matches the called program exactly. There is a way to help ensure that, and we will talk about it a little later.

Improving Our Prototype

Could our prototype be made more effective? I like to indicate in the prototype which parameters are used as "input only" in the called program by using the CONST ("constant reference" is the full term) keyword on those prototyped parameters. This not only helps by giving me more information about the data flow between this program and the called program, but more significantly, it allows the compiler to help me on the call. Consider the following example:

 

   D CalcTaxes       PR                  ExtPgm('PA0132')

   D  TotalPay                      7P 2 Const

   D  Allow_Nbr                     3S 0 Const

   D  FedTaxOutput                  5P 2

   D  StTaxOutput                   5P 2

      CalcTaxes( GrossPay + Bonus : Allow : FedTax : StateTax);

 

In this version, the first two parameters are labeled as CONST. Since the called program will not be changing their values to supply information back to this program, I'm able to simplify my logic somewhat by using the expression (GrossPay + Bonus) directly as a parameter, doing away with the PayAmt temporary field. Likewise, in this case, if the Allow field in this program was defined as packed decimal, it would not result in either a compile-time or a run-time error. Instead, the compiler would create a temporary field for each of the first two parameters, based on the data type and size specified in the prototype. It would then copy the appropriate values from this program into them to pass as parameters to the PA0132 program. 

 

While it doesn't help in this example, I could have even used a built-in function, such as %LEN, as a parameter, which is quite helpful when calling many system APIs. Note that the compiler does not make a temporary field and copy the data over unless it is necessary (i.e., when the parameters requested in the prototype do not match those used in the call statement).

 

Because the last two parameters are used as output parameters (i.e., their values are updated by the called program), they cannot be declared as constant; therefore, the fields passed for those parameters must match the data type and size specified by the prototype. 

Specifying Optional Parameters in the Caller

Another nice benefit of using prototypes is that I can now add additional optional parameters to the end of my parameter list. Let's say that the program that calculates taxes now needs a new parameter to indicate some kind of deduction that should be taken into account when calculating, e.g., a deduction for additional medical insurance and/or a retirement savings plan. However, there may still be some circumstances when that extra information does not need to be passed to the tax calculation program. To allow for both situations--sometimes the extra parameters are needed, but other times they are not--you could make the passing of the extra parameters optional by putting them at the end of the parameter list. This requires also using the *NoPass option keyword on the new optional parameters.

 

     D CalcTaxes       PR                  ExtPgm('PA0132')

     D  TotalPay                      7P 2 Const

     D  Allow_Nbr                     3S 0 Const

     D  FedTaxOutput                  5P 2

     D  StTaxOutput                   5P 2

     D  MedDeduct                     3P 2 Options(*NoPass)                            

     D  RetireDeduct                  3P 2 Options(*NoPass)

        CalcTaxes( GrossPay + Bonus : Allow : FedTax : StateTax );

        CalcTaxes(GrossPay + Bonus : Allow : FedTax : StateTax :     

                  Medical : Retire );

        CalcTaxes(GrossPay + Bonus : Allow : FedTax : StateTax :     

                  Medical );

 

In the example above, all three calls to the program are acceptable because of the use of the *NoPass option. Note that either the first or the first and second of the *NoPass parameters may be passed.

 

However, in this example, you cannot pass only the Retire parameter and not the Medical one. You could allow that scenario by adding the additional optional keyword *Omit in place of the fifth parameter. Actually, I rarely find this a useful feature, but since I'm often asked if it is an option, I'm including it in the example below. This illustrates the fourth possible call option for this revised prototype. Note the use of the RPG special value *Omit for the fifth parameter in the call statement. Note also that the called program must be prepared for both *Omit and *NoPass options to work properly; we'll look at how it does that in a moment.

 

   D CalcTaxes       PR                  ExtPgm('PA0132')

   D  TotalPay                      7P 2 Const

   D  Allow_Nbr                     3S 0 Const

   D  FedTaxOutput                  5P 2

   D  StTaxOutput                   5P 2

   D  MedDeduct                     3P 2 Options(*NoPass : *Omit)                            

   D  RetireDeduct                  3P 2 Options(*NoPass)

                                                     

      CalcTaxes(GrossPay + Bonus : Allow : FedTax : StateTax :     

            *Omit : Retire );

                    

What About the Called Program?

Finally, let's turn our attention to the called program: PA0132 in this case. We can call any program (or procedure) in any language using a prototype. However, if it is an RPG IV program you are calling, it makes sense to take full advantage of all that the compiler can do for you by replacing the called program's *Entry PList with a Procedure Interface (PI). Indeed, to fully move to /Free, you must do this because the *ENTRY PLIST is not supported in /Free. When you do this, the compiler can check your prototype for you to ensure you have coded it correctly. This is how you ensure the prototype in the calling program is correct, as mentioned earlier.

 

Note the following *Entry PList. We have gone back to the original example without the optional parameters. We'll add them back in later.

 

   C     *Entry        Plist    

   C                   Parm                    PayAmt

   C                   Parm                    Allow

   C                   Parm                    FedTax

   C                   Parm                    StateTax

 

This can (and should) be replaced with the following Procedure Interface:

 

   D CalcTaxes       PI                   

   D  TotalPay                      7P 2 Const

   D  Allow_Nbr                     3S 0 Const

   D  FedTaxOutput                  5P 2

   D  StTaxOutput                   5P 2

 

One thing to note here is that names on the PI (e.g., TotalPay, Allow_Nbr, etc.) are defining the fields of those names to hold the parameters. This is in contrast to the prototype where those "names" are documentary only.

 

There is a requirement for the prototype for the program (or procedure) to also appear in the source member at compile time. This enables the complier to conduct a check of the prototype's accuracy in matching the parameter list in the PI. Since we always want to ensure that the prototype that was verified as correct is used in all the calling programs, I highly recommend that you place your prototypes in a separate source member and that you use a /Copy to bring the prototype into this program (the called program) and also into any and all calling programs. That way, you can be sure that the prototype used in the callers is exactly the same as the one the compiler has already verified as matching the called program.

 

In the example below, note the use of the /Copy statement. The same /Copy statement should be used in the calling program as well, replacing the hard-coded prototypes used in the earlier illustrations. Note that I have also included the use of the optional parameters here that I utilized in the later versions of the prototype above.

 

If the source member named PA0132PR contains the following code...

 

   D CalcTaxes       PR                  ExtPgm('PA0132')

   D  TotalPay                      7P 2 Const

   D  Allow_Nbr                     3S 0 Const

   D  FedTaxOutput                  5P 2

   D  StTaxOutput                   5P 2

   D  MedDeduct                     3P 2 Options(*NoPass : *Omit) Const                           

   D  RetireDeduct                  3P 2 Options(*NoPass) Const

.

..then the source member PA0132 should contain the following:

 

    /Copy PA0132PR

    

   D CalcTaxes       PI                   

   D  TotalPay                      7P 2 Const

   D  Allow_Nbr                     3S 0 Const

   D  FedTaxOutput                  5P 2

   D  StTaxOutput                   5P 2

   D  MedDeduct                     3P 2 Options(*NoPass : *Omit) Const                           

   D  RetireDeduct                  3P 2 Options(*NoPass) Const

Handling Optional Parameters in the Called Program

Remember that I mentioned earlier that the called program must be prepared for the use of *NoPass and *Omit parameters? Let's now look at how this is done.

 

Since the *NoPass parameters require that all parameters be passed up until the first optional one, we can simply use the %Parms built-in function to determine the number of parameters that were passed and then take the appropriate action. For any *Omit parameters, we check for a *Null value in the parameter's address. Take a look at the following example:

 

   D CalcTaxes       PI                   

   D  TotalPay                      7P 2 Const

   D  Allow_Nbr                     3S 0 Const

   D  FedTaxOutput                  5P 2

   D  StTaxOutput                   5P 2

   D  Parm5                         3P 2 Options(*NoPass : *Omit) Const                           

   D  Parm6                         3P 2 Options(*NoPass) Const      

    

   D MedDeduct       S              3P 2  

   D RetireDeduct    S              3P 2  

    

    /Free

   

     If %Parms >= 5 and %Addr(Parm5) <> *Null;

        MedDeduct = Parm5;

      Else;

        MedDeduct = 0;

     EndIf;  

       

     If %Parms = 6;

        RetireDeduct = Parm6;

      Else;  

        RetireDeduct = 0;

     EndIf;  

 

Note the names of the last two (optional) parameters in the PI. It is critical that you never attempt to touch a parameter that was not passed to you, either because of *NoPass or *Omit. Therefore, elsewhere in the program logic, we will never again refer to fields Parm5 or Parm6. Instead, we will refer to the MedDeduct and RetireDeduct standalone fields to get the data. If either of those two parameters were not passed to the program, we'll simply plug in default values.

 

Note that in this case, we could have omitted the "Else" logic if the program returned with LR on each time because the two standalone fields would be initialized to 0 already. Also note that if the optional parameters were not constant, we would need to do a similar (but reversed) type of logic to populate the last two parameters after the calculations are done before returning to the caller.

 

The Options parameter for prototypes has several other useful capabilities, such as the following:

  • *VarSize allows character fields that are shorter than specified in the prototype to be passed. (Note that longer character fields can always be passed for character fields.)
  • *RightAdjust automatically right adjusts a character value passed as a CONST parameter.
  • *Trim automatically trims leading and/or trailing spaces from the value before passing it using a CONST parameter.

While you can only use it when calling bound procedures (not programs), another useful prototype keyword is VALUE. This tells the compiler to pass the actual value of the field rather than a pointer to the value in the calling program.

 

I hope this look into the power of RPG prototyping has helped you to understand more reasons to use prototypes than simply to allow a call in /Free logic. Utilizing the full capabilities of prototypes can make your life as a programmer easier and make your programs more bulletproof.

                                                         

Susan Gantner

Susan Gantner's career has spanned over 30 years in the field of application development. She began as a programmer, developing applications for corporations in Atlanta, Georgia, and working with a variety of hardware and software platforms. She joined IBM in 1985 and quickly developed a close association with the Rochester laboratory during the development of the AS/400 system. She worked in Rochester, Minnesota, for five years in the AS/400 Technical Support Center. She later moved to the IBM Toronto Software Laboratory to provide technical support for programming languages and AD tools on the AS/400 and iSeries.

Susan left IBM in 1999 to devote more time to teaching and consulting. She co-authored one of the most popular System i Redbooks ever, Who Knew You Could Do That with RPG IV? She and partner Jon Paris make up Partner400, a consulting company focused on education and mentoring services related to application modernization. Susan is also part of System i Developer, a consortium of top educators on System i technology who produce the RPG & DB2 Summit events. Its members include Jon Paris, Paul Tuohy, Skip Marchesani, and Susan Gantner.

BLOG COMMENTS POWERED BY DISQUS

LATEST COMMENTS

Support MC Press Online

$0.00 Raised:
$

Book Reviews

Resource Center

  • SB Profound WC 5536 Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application. You can find Part 1 here. In Part 2 of our free Node.js Webinar Series, Brian May teaches you the different tooling options available for writing code, debugging, and using Git for version control. Brian will briefly discuss the different tools available, and demonstrate his preferred setup for Node development on IBM i or any platform. Attend this webinar to learn:

  • SB Profound WP 5539More than ever, there is a demand for IT to deliver innovation. Your IBM i has been an essential part of your business operations for years. However, your organization may struggle to maintain the current system and implement new projects. The thousands of customers we've worked with and surveyed state that expectations regarding the digital footprint and vision of the company are not aligned with the current IT environment.

  • SB HelpSystems ROBOT Generic IBM announced the E1080 servers using the latest Power10 processor in September 2021. The most powerful processor from IBM to date, Power10 is designed to handle the demands of doing business in today’s high-tech atmosphere, including running cloud applications, supporting big data, and managing AI workloads. But what does Power10 mean for your data center? In this recorded webinar, IBMers Dan Sundt and Dylan Boday join IBM Power Champion Tom Huntington for a discussion on why Power10 technology is the right strategic investment if you run IBM i, AIX, or Linux. In this action-packed hour, Tom will share trends from the IBM i and AIX user communities while Dan and Dylan dive into the tech specs for key hardware, including:

  • Magic MarkTRY the one package that solves all your document design and printing challenges on all your platforms. Produce bar code labels, electronic forms, ad hoc reports, and RFID tags – without programming! MarkMagic is the only document design and print solution that combines report writing, WYSIWYG label and forms design, and conditional printing in one integrated product. Make sure your data survives when catastrophe hits. Request your trial now!  Request Now.

  • SB HelpSystems ROBOT GenericForms of ransomware has been around for over 30 years, and with more and more organizations suffering attacks each year, it continues to endure. What has made ransomware such a durable threat and what is the best way to combat it? In order to prevent ransomware, organizations must first understand how it works.

  • SB HelpSystems ROBOT GenericIT security is a top priority for businesses around the world, but most IBM i pros don’t know where to begin—and most cybersecurity experts don’t know IBM i. In this session, Robin Tatam explores the business impact of lax IBM i security, the top vulnerabilities putting IBM i at risk, and the steps you can take to protect your organization. If you’re looking to avoid unexpected downtime or corrupted data, you don’t want to miss this session.

  • SB HelpSystems ROBOT GenericCan you trust all of your users all of the time? A typical end user receives 16 malicious emails each month, but only 17 percent of these phishing campaigns are reported to IT. Once an attack is underway, most organizations won’t discover the breach until six months later. A staggering amount of damage can occur in that time. Despite these risks, 93 percent of organizations are leaving their IBM i systems vulnerable to cybercrime. In this on-demand webinar, IBM i security experts Robin Tatam and Sandi Moore will reveal:

  • FORTRA Disaster protection is vital to every business. Yet, it often consists of patched together procedures that are prone to error. From automatic backups to data encryption to media management, Robot automates the routine (yet often complex) tasks of iSeries backup and recovery, saving you time and money and making the process safer and more reliable. Automate your backups with the Robot Backup and Recovery Solution. Key features include:

  • FORTRAManaging messages on your IBM i can be more than a full-time job if you have to do it manually. Messages need a response and resources must be monitored—often over multiple systems and across platforms. How can you be sure you won’t miss important system events? Automate your message center with the Robot Message Management Solution. Key features include:

  • FORTRAThe thought of printing, distributing, and storing iSeries reports manually may reduce you to tears. Paper and labor costs associated with report generation can spiral out of control. Mountains of paper threaten to swamp your files. Robot automates report bursting, distribution, bundling, and archiving, and offers secure, selective online report viewing. Manage your reports with the Robot Report Management Solution. Key features include:

  • FORTRAFor over 30 years, Robot has been a leader in systems management for IBM i. With batch job creation and scheduling at its core, the Robot Job Scheduling Solution reduces the opportunity for human error and helps you maintain service levels, automating even the biggest, most complex runbooks. Manage your job schedule with the Robot Job Scheduling Solution. Key features include:

  • LANSA Business users want new applications now. Market and regulatory pressures require faster application updates and delivery into production. Your IBM i developers may be approaching retirement, and you see no sure way to fill their positions with experienced developers. In addition, you may be caught between maintaining your existing applications and the uncertainty of moving to something new.

  • LANSAWhen it comes to creating your business applications, there are hundreds of coding platforms and programming languages to choose from. These options range from very complex traditional programming languages to Low-Code platforms where sometimes no traditional coding experience is needed. Download our whitepaper, The Power of Writing Code in a Low-Code Solution, and:

  • LANSASupply Chain is becoming increasingly complex and unpredictable. From raw materials for manufacturing to food supply chains, the journey from source to production to delivery to consumers is marred with inefficiencies, manual processes, shortages, recalls, counterfeits, and scandals. In this webinar, we discuss how:

  • The MC Resource Centers bring you the widest selection of white papers, trial software, and on-demand webcasts for you to choose from. >> Review the list of White Papers, Trial Software or On-Demand Webcast at the MC Press Resource Center. >> Add the items to yru Cart and complet he checkout process and submit

  • Profound Logic Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application.

  • SB Profound WC 5536Join us for this hour-long webcast that will explore:

  • Fortra IT managers hoping to find new IBM i talent are discovering that the pool of experienced RPG programmers and operators or administrators with intimate knowledge of the operating system and the applications that run on it is small. This begs the question: How will you manage the platform that supports such a big part of your business? This guide offers strategies and software suggestions to help you plan IT staffing and resources and smooth the transition after your AS/400 talent retires. Read on to learn: