How to Use the *NOPASS and *OMIT Parameter Options
January 5, 2005 Hey, Joel
I notice that you use *nopass extensively in your free xRPG procedures to offer flexibility in your procedure calls, but you don’t seem to use *omit. I recently came across an example of a prototype that used both for the same parameter. Can you explain the difference between the two, why you use one but not the other, and why someone would use them together like this?
–Rob
Let me start by explaining the two options and how they differ.
*NOPASS
The *nopass parameter option allows you to not pass the parameter at all. Consider the prototype for the getDateStrUSA procedure from my xRPG Core Library :
d getDateStrUSA pr 10 varying d date d const options( *nopass ) d zero n const options( *nopass ) d inc n const options( *nopass ) d sep 1 const options( *nopass )
You’ll notice right away that all the parameters use options(*nopass), meaning that they are completely optional. The first rule of *nopass is that every parameter after an optional one is also optional. This means that I have five different parameter lists for calling this procedure:
/free // Get Todays Date as a USA Formatted Date String usaDateString = getDateStrUSA(); // Get This Date as a USA Formatted Date String usaDateString = getDateStrUSA( dateField ); // Get This Date as a USA Formatted Date String, // not zero-suppressed usaDateString = getDateStrUSA( dateField : *off ); // Get This Date as a USA Formatted Date String, // not zero-suppressed, with separators usaDateString = getDateStrUSA( dateField : *off : *on ); // Get This Date as a USA Formatted Date String, // not zero-suppressed, with separators, use "." as separator usaDateString = getDateStrUSA( dateField : *off : *on : '.' ); /end-free
You’ll notice the functionality changes with each parameter, again adding to the flexibility and usefulness of this procedure. This does require some coding discipline in the procedure. First of all, when you allow optional parameters, you must check inside the procedure to see whether the parameters were sent. This is done with the %parms() built-in function.
You also must be aware that while a parameter in a procedure interface defines a local variable in the procedure, you must not use the parameter if it is not sent. The best way I’ve seen to handle this is to define variables within the procedure that match the incoming parameters and then use the parameter list to set their values. This also allows you to easily establish behavioral defaults for the procedure that are used in the absence of the sent parameters. Below is the code from my getDateStrUSA procedure that handles the parms coming in:
d getDateStrUSA pi 10 varying d date d const options( *nopass ) d zero n const options( *nopass ) d inc n const options( *nopass ) d sep 1 const options( *nopass ) d dateIn s d d zeroSuppress s n inz( *on ) d incSeps s n inz( *on ) d sepChar s 1 inz( '/' ) /free select ; when %parms() = 0 ; dateIn = %date(); when %parms() = 1 ; dateIn = date ; when %parms() = 2 ; dateIn = date ; zeroSuppress = zero ; when %parms() = 3 ; dateIn = date ; zeroSuppress = zero ; incSeps = inc ; when %parms() = 4 ; dateIn = date ; zeroSuppress = zero ; incSeps = inc ; sepChar = sep ; endsl ; /end-free
Notice how I set the default values in the procedure’s D–specs. These values are then overwritten if the parameters were sent. Now the rest of the code uses the Standalone fields defined in the procedure and not the parameter variables defined in the Procedure Interface.
*OMIT
The *omit parameter option allows you to not send a value for a parameter, but, unlike *nopass, it does require you to send a placeholder. If you are not going to send a value, *null must be sent in the parameter location. Using the same example procedure from above, had I used *omit instead of *nopass, the prototype would look like so:
d getDateStrUSA pr 10 varying d date d const options( *omit ) d zero n const options( *omit ) d inc n const options( *omit ) d sep 1 const options( *omit )
Using *omit would also change the way I can call this procedure:
/free // Get Todays Date as a USA Formatted Date String usaDateString = getDateStrUSA( *omit : *omit : *omit : *omit ); // Get This Date as a USA Formatted Date String usaDateString = getDateStrUSA( dateField : *omit : *omit : *omit ); // Get This Date as a USA Formatted Date String, // not zero-suppressed usaDateString = getDateStrUSA( dateField : *off : *omit : *omit ); // Get This Date as a USA Formatted Date String, // not zero-suppressed, with separators usaDateString = getDateStrUSA( dateField : *off : *on : *omit ); // Get This Date as a USA Formatted Date String, // not zero-suppressed, with separators, use "." as separator usaDateString = getDateStrUSA( dateField : *off : *on : '.' ); /end-free
I hope you agree that this is less elegant than *nopass. The good news is that, unlike *nopass, which requires every parameter after a nopass parameter to also be *nopass, you can pick and choose which parameters you want to be omittable.
Handling the parameter checking in the procedure code is also different. Instead of checking for whether the parameter has been sent, you have to check whether a value or the *null placeholder was sent, by using the %addr() BIF.
d getDateStrUSA pi 10 varying d date d const options( *omit ) d zero n const options( *omit ) d inc n const options( *omit ) d sep 1 const options( *omit ) d dateIn s d d zeroSuppress s n inz( *on ) d incSeps s n inz( *on ) d sepChar s 1 inz( '/' ) /free if %addr( date ) = *NULL ; dateIn = %date(); else ; dateIn = date ; endif ; if %addr( zero ) <> *NULL ; zeroSuppress = zero ; endif ; if %addr( inc ) <> *NULL ; incSeps = inc ; endif ; if %addr( sep ) <> *NULL ; sepChar = sep ; endif ; /end-free
Again, I can rely on the default values in the procedure and only change them if the parameter was not omitted.
Using Both Together
One interesting approach is to combine *nopass and *omit:
d getDateStrUSA pr 10 varying d date d const options( *nopass : *omit ) d zero n const options( *nopass : *omit ) d inc n const options( *nopass : *omit ) d sep 1 const options( *nopass : *omit )
I now have an interesting assortment of calling parameter lists. I won’t list them all here, but I will answer the question I’m sure you are asking right now: why would anyone do this?
Take the procedure I’ve been using as an example. The parameters have a series of defaults for the optional parameters. If the programmer wants to use option 4 to change the separator character to something other than the default, but wants the default behavior for parameters 1, 2, and 3, then he must know what values to pass to represent those defaults. Using this approach still allows the caller to have flexibility, without requiring the programmer to know what values to send to represent the default behavior. In this case, it could look like this instead:
/free // Get This Date as a USA Formatted Date String, // zero-suppressed, with separators, use "." as separator usaDateString = getDateStrUSA( *omit : *omit : *omit : '.' ); /end-free
Now the programmer doesn’t have to research to remember that he should send %date(), *on, *on for the first three parameters. This could also allow the developer to change the default procedure behaviors without the programmer needing to change the calling code. I’ll leave it to the reader to determine the pros and cons of that approach.
Using both options also increases the work you must do in order to handle those incoming parameters. If sent, the *omit placeholder counts as a parameter, just one whose value is null. So basically you have to check for whether a parm was sent and whether it has a value of null:
d getDateStrUSA pi 10 varying d date d const options( *nopass : *omit ) d zero n const options( *nopass : *omit ) d inc n const options( *nopass : *omit ) d sep 1 const options( *nopass : *omit ) d dateIn s d d zeroSuppress s n inz( *on ) d incSeps s n inz( *on ) d sepChar s 1 inz( '/' ) /free if %parms() = 0 ; dateIn = %date(); endif ; if %parms() > 0 ; if %addr( date ) = *NULL ; dateIn = %date(); else ; dateIn = date ; endif ; endif ; if %parms()> 1 ; if %addr( zero ) <> *NULL ; zeroSuppress = zero ; endif ; if %parms()> 2 ; if %addr( inc ) <> *NULL ; incSeps = inc ; endif ; endif ; if %parms()> 3 ; if %addr( sep ) <> *NULL ; sepChar = sep ; endif ; endif ; /end-free
It is true that I favor *nopass over *omit, almost to the point of exclusivity. Originally this was a conscious choice, because the logic of *omit didn’t make a lot of sense to me. I mean that if I have to send a parameter, why not send the correct value as well? However, as I develop more and more procedures for other programmers, I find that practical flexibility is of extreme value. For the cost of a little extra parameter checking, I can use both options together to add even more flexibility to a procedure. To me, that is something very worthwhile.
–Joel Cochran
Joel Cochran is the director of research and development for a small software firm in Staunton, Virginia, and is the author and publisher of www.RPGNext.com. Click here to contact Joel by e-mail.
This article has been corrected since its original publication. In the last section of code, the “date” and “dateIn” variable names were presented in the wrong order. IT Jungle regrets the error. [Correction made 1/6/05]