close window

Print print

The Case of the Missing Parameters

Prototypes is a topic weve covered before. In a previous article, we explained the basics of how and why to prototype the parameters for program calls and introduced you to the joys of the CONST keyword. Here, well outline other keywords that you may find useful and cover a few more areas related to prototypes.

 

The OPTIONS keyword is used on parameter fields in a prototype, much like CONST, and has several potential values. The most commonly used of these is Options(*NoPass). This allows you to specify that a parameter is optional. Lets examine a simple example.

Suppose youve written a procedure that calculates the last day of the month for any date passed to it.

The prototype for this procedure might resemble this:

D LastDayMonth    PR              D   DatFmt(*USA)
D   WorkDate                      D   DatFmt(*USA)

As you can see, it takes a *USA date as the parameter and returns a date in the same format. Now suppose that you need to introduce a new version of the procedure that allows the date to be advanced by a specified number of months before calculating the last day. Of course one option would be to simply create a completely new procedure, but a better option would be to extend the functionality of the existing routine. However, if were to simply add an extra parameter, we would be forced to find every location where the existing routine was used and add the additional parameter.

Options(*NoPass) provides a better way. First, modify the prototype to look like this:

  D LastDayMonth    PR              D   DatFmt(*USA)
  D   InpDate                       D   DatFmt(*USA)
  D   Months                       3P 0 Options(*NoPass)

This allows the procedure to be called with a single parameter, like this:

C                   Eval     Day = LastDayMonth(WorkDate)

Or with two parameters, like this:

C                   Eval     Day = LastDayMonth(WorkDate: MonthsAdv)

Of course to accommodate existing callers of this procedure, we must modify the procedure itself so that it will work as it did originally for callers passing only a single parameter.

 


To accomplish this, the code in the LastDayMonth procedure must be changed to react to both situations. The easiest way to detect whether the second parameter was actually passed is to check the value of the %Parms built-in function. If its value is 1, then the optional months parameter isnt present. If the value is 2, then the parameter was supplied.

 

The calcs in the original code looked like this:

 // Advance the date passed to us by one month
C                   Eval       TempDate = InpDate + %Months(1)

 // Subtract day number from the date to reach last day of prior month
C                   Eval       TempDate = TempDate
C                                    - %Days( %SubDt( TempDate: *D))

C                   Return     TempDate

To accommodate the new optional parameter, simply replace the first line with this conditional logic:

C                   If        %Parms = 1
 // Advance the date passed to us by one month
C                   Eval      TempDate = InpDate + %Months(1)
C                   Else
 // Or by one plus the number of months passed
C                   Eval      TempDate = InpDate + %Months(Months + 1)
C                   EndIf

As time goes by, we might choose to add further optional parameters to this procedure. The only requirement is that any parameters added to the end of the list are also designated as *NoPass. Furthermore, when calling such a procedure, once you have omitted a parameter, you must also omit the remaining parameters as well.

Important note: Its vital that you never reference a parameter that wasnt passed to you. Not because a pointer error will occur, but because it may not. This may come as a surprise to some of you. After all, an error is exactly what would happen if you attempted to reference more parameters than were passed on a conventional program call. However, bound calls are different, and subprocedures use the bound-call mechanism. We dont want to get into the mechanics of why this happens; suffice to say that in many cases, its likely that a valid pointer exists in the position where the Months parameter would have been. What field would you actually be referencing? Your guess is as good as ours! It could be anything. So its critical to include logic to check %Parms when using *NoPass parameters-not because youll get an error if you dont, but because you may not.

What if you wanted to omit a parameter that wasnt at the end of the parameter list? In this case, the option you need is *Omit, which means that you must pass a value for the parameter, but the value may be null (no value). To achieve this in RPG, you would pass the special value *Omit.

In our example, suppose we wanted to allow the caller to omit the input date parameter. If the parameter were omitted, the procedure assumes the current system date for the value. In this case, the prototype might look like this:

  D   LastDayMonth     PR              D   DatFmt(*USA)
  D     InpDate                        D   DatFmt(*USA)Options(*Omit)
  D     Months                        3P 0 Options(*NoPass)

Note that the callers could now do any of the following, in addition to the two aforementioned options:

C                   Eval      Day = LastDayMonth(*Omit)
C                   Eval      Day = LastDayMonth(*Omit: MonthsAdv)

Again, the LastDayMonth procedure must check for the possibility of the omitted parameter and act accordingly. However, %Parms cant help us this time since the *Omit parameter will be included in the count. So how do you test for an omitted parameter? The easiest way is to test its address using %Addr. It would be nice if there was a %Omit built-in function, but there isnt. In fact for some reason, you cannot even test the %Addr value against *Omit; rather you must check it against *Null. So the logic to check if the first parameter was actually passed would be:

C                    If        %Addr(InpDate) <> *Null

Can you mix both *NoPass and *Omit? Certainly. The following prototype is perfectly valid-well leave it as an exercise for you to work out the valid call combinations.

  D   LastDayMonth     PR              D   DatFmt(*USA)
  D     InpDate                        D   DatFmt(*USA)
  D                                        Options(*Omit: *NoPass)
  D     Months                        3P 0 Options(*NoPass)

Note that while weve been detailing the use of Options(*NoPass) and Options(*Omit) in conjunction with subprocedures, they may also be used in the prototypes used for calling programs and bound main procedures.

Weve just scratched the surface of using prototype parameter keywords. Well cover more of the many other available parameter keywords in future articles.