Do you know the best way to use optional parameters with procedures?
When you're using procedures, you have to ability to support optional parameters by using the special keywords *NOPASS and *OMIT. This article discusses the difference between the two and the appropriate times to use them.
*NOPASS
Let's begin with the *NOPASS option. The *NOPASS keyword specifies that the parameter is an optional parameter and does not need to be passed.
When you specify a parameter as being optional with the *NOPASS keyword, any subsequent parameters must also be specified as *NOPASS. This makes sense because, if you were allowed to skip parameters, how would the procedure being called be able to figure out which parameters you intend to send?
Determining How Many Parameters Were Passed
Once you specify parameters as being optional, you need to be able to support them within your procedure. Because the variables are not initialized, if you try to access them within the procedure, your program will crash or it will throw an exception if you are monitoring for it. To see how many parameters were passed, you can use the %parms built-in function (BIF) inside of your procedure. The %parms BIF will return the number of parameters that were passed into the procedure.
Creating a Procedure to Use *NOPASS
To illustrate the use of the *NOPASS special keyword, let's create a procedure that uses this functionality. For our example, we will create a procedure that uses the CPYTOIMPF command to export files to the IFS. This will allow us to easily export physical files with a simple line of code in our RPG programs.
For our programs, we will use the QCMDEXC API to execute our command. Here's the prototype for that:
D* Prototype for QCMDEXC API
D ExecuteCommand...
D PR extPgm('QCMDEXC')
D argInCommand 65535A const options(*varsize)
D argInLength 15P 5 const
To illustrate the use of the *NOPASS keyword, we will create a procedure that calls the CPYTOIMPF command called exportFile. Here's the prototype for that:
D* Prototype for CPYIMPF procedure
D exportFile...
D PR 1N
D argFromFile 10A const
D argFromLib 10A const options(*NOPASS)
D argFromMbr 10A const options(*NOPASS)
D argToFile 512A const options(*NOPASS)
The first parameter is the physical file that we are exporting from. Because we are exporting a file, this parameter is required, so I did not use the *NOPASS option on this one. But all the rest can be defaulted to another value, so these have been assigned to be optional using the *NOPASS keyword.
And here is our exportFile procedure using the *NOPASS keyword on three of the four parameters:
P exportFile...
P B EXPORT
D exportFile...
D PI 1N
D argFromFile 10A const
D argFromLib 10A const options(*NOPASS)
D argFromMbr 10A const options(*NOPASS)
D argToFile 512A const options(*NOPASS)
D* Local Variables
D svReturn S 1N
D svToFile S 512A
D svCmdString S 2000A
/free
svReturn = *OFF;
//---------------------------------------------------------
// Initialize the Defaults.
//---------------------------------------------------------
svToFile = %trim(argFromFile) + '.txt';
if %parms > 3;
svToFile = %trim(argToFile);
endif;
//-------------------------------------------------------------
// CPYTOIMPF
//-------------------------------------------------------------
svCmdString = 'CPYTOIMPF FROMFILE(';
// Library (Optional)
if %parms > 1;
svCmdString = %trim(svCmdString) + %trim(argFromLib) + '/';
endif;
// File (Mandatory)
svCmdString = %trim(svCmdString) + %trim(argFromFile);
// Member (Optional)
if %parms > 2;
svCmdString = %trim(svCmdString) + ' ' + %trim(argFromMbr);
endif;
svCmdString = %trim(svCmdString) + ') '
+ 'TOSTMF(''' + %trim(svToFile) + ''') MBROPT(*REPLACE) '
+ 'STMFCCSID(*PCASCII) RCDDLM(*CRLF) DTAFMT(*FIXED)';
monitor;
ExecuteCommand(%trim(svCmdString):%len(%trim(svCmdString)));
on-error;
// Exception
svReturn = *ON;
endmon;
return svReturn;
/end-free
P E
For the library and the physical file member, we have dynamically built the CPYTOIMPF command to include them into the command to be executed if they were passed into the procedure. The target stream file to be created on the IFS will be defaulted to match the name of the physical file with a .txt extension if one was not specified.
To test out our procedure, we'll try a different permutation of each option available from the main procedure:
/free
if (exportFile('EMPHIST'));
dsply 'Error on Export!';
endif;
if (exportFile('EMPHIST':'MYLIB'));
dsply 'Error on Export!';
endif;
if (exportFile('EMPHIST':'MYLIB':'EMPHIST'));
dsply 'Error on Export!';
endif;
if (exportFile('EMPHIST':'MYLIB':'EMPHIST':
'/Public/EmployeeHistory.txt'));
dsply 'Error on Export!';
endif;
*inlr = *ON;
/end-free
For our code sample, we allow the library and the member to be optional parameters and we set default values if they are not passed.
What if we want to use the default for the library but want to pass the member? Or what if we want to use the defaults for the library and member but specify the output file name? This is where the *OMIT keyword comes into play.
*OMIT
The *OMIT keyword indicates that the parameter is optional, but it will use the *OMIT special value as a placeholder to indicate that it is not being passed. Then we could have additional parameters after the omitted parameter. And with *OMIT, we do not have to specify the rest of the parameters as being omitted. This is because the optional parameter is being identified.
The *OMIT does identify the parameter as being valid, so you can't use the %parms keyword to determine if a parameter was omitted or not. And even if you could, if you had multiple omitted parameters, you wouldn't be able to determine which one was omitted—unless all of them were. So we could use the %ADDR to identify whether the parameter was omitted because the address will be null if it was omitted.
Here is our new version of the prototype that uses both *NOPASS and *OMIT:
D* Prototype for CPYIMPF procedure
D exportFile...
D PR 1N
D argFromFile 10A const
D argFromLib 10A const
D options(*NOPASS: *OMIT)
D argFromMbr 10A const
D options(*NOPASS: *OMIT)
D argToFile 512A const
D options(*NOPASS: *OMIT)
And here is our new exportFile procedure using the *OMIT keyword:
P exportFile...
P B EXPORT
D exportFile...
D PI 1N
D argFromFile 10A const
D argFromLib 10A const
D options(*NOPASS: *OMIT)
D argFromMbr 10A const
D options(*NOPASS: *OMIT)
D argToFile 512A const
D options(*NOPASS: *OMIT)
D* Local Variables
D svReturn S 1N
D svToFile S 512A
D svCmdString S 2000A
/free
svReturn = *OFF;
//---------------------------------------------------------
// Initialize the Defaults.
//---------------------------------------------------------
svToFile = %trim(argFromFile) + '.txt';
if %parms > 3;
if %addr(argToFile) <> *NULL;
svToFile = %trim(argToFile);
endif;
endif;
//-------------------------------------------------------------
// CPYTOIMPF
//-------------------------------------------------------------
svCmdString = 'CPYTOIMPF FROMFILE(';
// Library (Optional)
if %parms > 1;
if %addr(argFromLib) <> *NULL;
svCmdString = %trim(svCmdString) + %trim(argFromLib) + '/';
endif;
endif;
// File (Mandatory)
svCmdString = %trim(svCmdString) + %trim(argFromFile);
// Member (Optional)
if %parms > 2;
if %addr(argFromMbr) <> *NULL;
svCmdString = %trim(svCmdString) + ' ' + %trim(argFromMbr);
endif;
endif;
svCmdString = %trim(svCmdString) + ') '
+ 'TOSTMF(''' + %trim(svToFile) + ''') MBROPT(*REPLACE) '
+ 'STMFCCSID(*PCASCII) RCDDLM(*CRLF) DTAFMT(*FIXED)';
monitor;
ExecuteCommand(%trim(svCmdString):%len(%trim(svCmdString)));
on-error;
// Exception
svReturn = *ON;
endmon;
return svReturn;
/end-free
P E
It's not necessary to use *NOPASS with *OMIT. We could just specify *OMIT, which would require us to always pass the parameter and specify the *OMIT keyword if we want it to be omitted. I use a combination of both here for the most flexibility.
The main procedure will test out the modified exportFile procedure with the *OMIT keyword specified on the arguments. All of the tests in the previous main procedure will work because we still have *NOPASS specified, but now we can select which parameters will be passed and which ones will not.
/free
// Specify: File and Output File Name
if (exportFile('EMPHIST':*OMIT:*OMIT:
'/Public/EmployeeHistory1.txt'));
dsply 'Error on Export!';
endif;
// Specify: File, Library and Output File Name
if (exportFile('EMPHIST':'MYLIB':*OMIT:
'/Public/EmployeeHistory2.txt'));
dsply 'Error on Export!';
endif;
// Specify: File, Member and Output File Name
if (exportFile('EMPHIST':*OMIT:'EMPHIST':
'/Public/EmployeeHistory3.txt'));
dsply 'Error on Export!';
endif;
// Specify: Everything
if (exportFile('EMPHIST':'MYLIB':'EMPHIST':
'/Public/EmployeeHistory4.txt'));
dsply 'Error on Export!';
endif;
// Specify: Everything but Output File Name (Using *OMIT)
if (exportFile('EMPHIST':'MYLIB':'MCPRESS':*OMIT));
dsply 'Error on Export!';
endif;
// Specify: Everything but Output File Name (Using *NOPASS)
if (exportFile('EMPHIST':*OMIT:'MCPRESS'));
dsply 'Error on Export!';
endif;
*inlr = *ON;
/end-free
On review of this main procedure, you can see that you could selectively pass whichever values you want to use to override the default values, or you could not pass them at all.
CPYTOIMPF (STMFCODPAG Changed to STMFCCSID)
Notice that I am using the STMFCCSID parameter with the CPYTOIMPF command because I'm using V6R1. The V5R4 box used STMFCODPAG, which is still supported but is not listed when you prompt the command and is intended to be removed in a later release.
If you use this procedure to perform your exports, you only need to change the syntax in one procedure, and all of your programs using this functionality will be updated.
Using *OMIT on the Last Parameter
You don't need to specify *OMIT on the last parameter; you don't really need a placeholder here because there is nothing after it. But, when I'm building reusable procedures that I intend to use in the future, I like to code them in such a way that I have to do minimal thinking when using them. So, if I were to assume I could use *OMIT for all optional parameters, then my procedure will support it because my procedure is programmed to handle it. That way, if I were to pass in *OMIT in the last parameter, it will not see it as a valid file name and will behave as expected. And who knows, you may add additional parameters later on.
The Output
If you view the output using Qshell, you can see the files that were created. You can enter Qshell by typing STRQSH on the command line.
Figure 1: Use Qshell to see the files created on the IFS. (Click images to enlarge.)
For the files that were exported without a specified name, the default name was assigned to match the name of the physical file with a .txt extension. All the rest were placed into the Public folder, as specified within the program.
Download the Code
You can download the code used in this article by clicking here.
as/400, os/400, iseries, system i, i5/os, ibm i, power systems, 6.1, 7.1, V7,
LATEST COMMENTS
MC Press Online