The *NOPASS option is very useful, but it's not the perfect solution for every situation. This TechTip continues to discuss the different options available, explaining what *OMIT is and when and how to use it.
What if you need the 21st and 22nd parameters of a procedure's parameter list with 40 parameters to be optional? Should you change the procedure's parameters, making the 23rd to 40th parameters optional too? That means validating whether all those parameters were passed and assigning default values to each of them. Not the best of solutions, right? This is the most annoying shortcoming of *NOPASS.
Fortunately for you and me (and all the other RPG programmers out there), the great minds behind RPG thought about this and came up with another option, similar to *NOPASS, that doesn't have this problem. The *OMIT option allows you to make a parameter optional, without having to do the same to all the parameters that come after it in the list. As you might expect, it also requires a bit of coding to use safely. Let's continue to use the previous TechTip's example: our USD_to_EUR function will have its functionality extended and will now convert any valid currency to EUR and return a status code as an output parameter. If I'd use *NOPASS, things might get cumbersome as the number of parameters grows, making it harder to code and understand later (yes, because most code is read way more often than it's written/modified). So the new Prototype Definition is as follows:
*------------------------------------------------------------------------*
* Cvt_To_Eur: Converts a currency to Eur at the current exchange rate
* This function accepts an Amount (11, 2)
* An optional date (8, 0)
* And currency ISO code (3, A) as input parameters.
* It also includes a status code (4, 0) as output parameter.
* It returns an EUR Amount (11, 2)
*------------------------------------------------------------------------*
D Cvt_to_Eur PR 11 2
* Input parameter: Amount in US Dollars
D P_USD_Amt 11 2 Value
* Input parameter: exchange rate date (latest available if not specified)
D P_Rate_Date 8 0 OPTIONS(*OMIT)
* Input parameter: currency ISO code (defaults to USD if not specified)
D P_Curr_Code 8 0 OPTIONS(*OMIT)
* Output parameter: status code
D P_Status_code 3A
As I said before, the Prototype Interface would also need to be changed in a similar manner.
Anyway, the first thing to note here is that there are two optional parameters in the middle of the list, which is not possible with *NOPASS. Great! Then why use *NOPASS at all? Well, *OMIT also has a peculiar "feature" that can be a bit annoying: in order to call a function that has parameters with this option, you have to pass all the parameters on every call. If you don't want to specify a parameter, just pass the *OMIT special value in its place. I'm not sure if you remember, but with *NOPASS, you can choose not to pass the optional parameters at all. Let me illustrate this with an example of a typical call of each function:
* Calling USD_TO_EUR without the optional parameter
C EVAL W_EUR_Amt = USD_to_EUR(W_USD_Amt)
* Calling Cvt_TO_EUR without the optional parameters
C EVAL W_EUR_Amt = Cvt_to_EUR(W_USD_Amt :
C *Omit :
C *Omit :
C W_StsCode)
Since you have to pass all the parameters on every call, the %PARMS BIF can't be used to determine if the optional parameter was passed or not. Instead, you have to use the %ADDR BIF to determine if the *OMIT special value was passed. When you pass *OMIT, there's no memory address for your variable because you're not really passing a value; in other words, its memory address is void or *NULL. The code below could be part of the Cvt_to_EUR function:
* Check if the date was passed and fill W_Rate_Date accordingly
C IF %ADDR(P_Rate_Date) <> *NULL
C EVAL W_Rate_Date = P_Rate_Date
C ELSE
C EVAL W_Rate_Date = *HIVAL
C ENDIF
* Check if the Curr. Code was passed and fill W_Curr_Code accordingly
C IF %ADDR(P_Curr_Code) <> *NULL
C EVAL W_Curr_Code = P_Curr_Code
C ELSE
C EVAL W_Curr_Code = 'USD'
C ENDIF
* Then use the W_Curr_Code to look for the appropriate rate
As you can see, it's similar to the *NOPASS/%PARM combination. The %ADDR is used to determine if the *OMIT special value was used and, just like before, the work variable is set accordingly—in this case, using a default value.
Finally, a few notes of caution about these two options:
- Be sure to check every single parameter that uses *NOPASS or *OMIT with the appropriate BIF.
- Always define a work variable similar to the parameter that you need to test with a name that somehow links the parameter and work variable together. For instance, use the same name with different prefixes: P_ for the parameter and W_ for the work variable.
- Decide what to do when the parameter is not passed or omitted before you start to write the code. This can be assigning a default value to the work variable, executing a different piece of code, or some other task, in order to keep the procedure or function working properly.
If you really want to complicate things, you can use both options in the same parameter:
(…)
D P_Some_Parm 8 0 OPTIONS(*NOPASS : *OMIT)
(…)
I never actually had to use this, but I'm sure that there are situations in which it might be useful.
Just like the VALUE vs. CONST discussion, there's no right or wrong solution when it comes to the *NOPASS vs. *OMIT debate. It's up to you to decide when to use each, carefully considering the pros and cons of each approach and choosing the best for the case at hand.
The next TechTip will be about the remaining options. As usual, feel free to share your thoughts on this in the comments!
LATEST COMMENTS
MC Press Online