Even though we have used /Free to build some ILE programs, there are still a few more tricks for you to learn about the latest iteration of RPG.
Editor's Note: This article is excerpted from article 11 of 21st Century RPG: /Free, ILE, and MVC, by David Shirey.
We are going to start by talking about two related topics: function calls and return variables.
The first thing I want to say is that this is a technique that can only be used to call a sub-procedure that is embedded in a service program or that is embedded in the module you are doing the function call from. Remember before how we did that with a CALLP? Well this is another way to do that without the CALLP. But the function call cannot be used to call one program from another; that must still be done with a CALLP. It only works when calling a sub-procedure.
The second thing I want to say is these two things (function calls and return values) go together. You can’t do one without the other.
What Are “These Things”?
The function call allows you to dispense with the CALLP opcode and just express the “call or prototype” as an equality between a variable and a function, similar to the way you would in PHP or another Web language. It’s kind of freaky, and I have a certain tendency toward vertigo when I use it, but it is very popular in Web languages, will get more common in RPG as we move into the future, and is way cheaper than Ecstasy.
Return values deal with getting something back from the called program, something that is not one of the parameters that is passed with the prototype. That is, we have already seen how you can send parameters along with the call and then get them back when the CALLP is over. But what if you want to have other variables returned from the sub-procedure or program beyond what is in the parameters? That is where return values come in. And a function call (as opposed to CALLP) is the only way you can work return values into the mix.
But remember, this type of call only works when you are calling a sub-procedure (either in a service program or one embedded in the program you issue the call from). You cannot use it to call an independent program.
Compile Notes
I really shouldn’t bother you with this because it’s pretty obvious if you think about it, but—well, sometimes we don’t think unless we are told to, so let me just mention this.
If you are doing the function call/return values thing from a program to a service program, then remember that both programs will have to be compiled first with CRTRPGMOD (instead of CRTBNDRPG). Because of this, you won’t need the H-spec for DFTACTGRP in either program (because CRTRPGMOD assumes you are going to be ILE). But you can include an H-spec giving the ACTGRP that you want to use (more on what those activation groups are later). Then you will need to do the CRTSRVPGM for the called program (the service program) and the CRTPGM for the calling program.
If you are doing the function call/return values thing from a program that actually contains the embedded sub-procedure, then you can use the DFTACTGRP H-spec and just compile the program as CRTBNDRPG.
Function Call/Return Values: The Details
Let’s start with the function call. What is that again?
It is something that is actually very Web language-like: the ability to call a procedure through an evaluate statement. Hold it. Wait a minute! Where are you going?? Come back. It’s not hard to do. Honest! See, it doesn’t look scary. A little weird maybe.
DESCP = VAL_PRDNO(PRDNO);
That is, we call the sub-procedure, VAL_PRDNO, by equating it to a variable DESCP, which is defined in the sub-procedure. It has to be defined in the calling program as well; my point here is that it is coming from the sub-procedure and so needs to be valid in there as well.
This sub-procedure could be held in a service program whose name I could give a rip about because when dealing with service programs you never call the service program, just the sub-procedure (VAL_PRDNO).
Or the sub-procedure could be embedded in the program that we are in. What is important is that we are doing the call by doing an evaluate to a variable, DESCP.
Why DESCP? Because that is the variable (a description field) whose value we want to return from the sub-procedure. That is, DESCP is the “return value” of the “function call.” We pick what it is. As long as it is described in the program we are calling.
Please notice that DESCP is not a parm in the prototype that is defined for the VAL_PRDNO sub-procedure. But by using the function call and a few modifications to our calling and called program, we let the compiler know that we will be returning whatever variable is on the left side of an equality involving the sub- procedure. And yes, you are right. This is as close to black magic as you can get without actually having to drink blood.
As you can see, return values are inextricably linked to the function call. What happens if you do the function call but you don’t set things up to return a value? It doesn’t work. The compile fails because function calls and return variables go together like Beckett and Captain Hammer.
Similarly, if you want to do a return value, then you have to use the function call. That is, if you want to return a value outside of the parms listed in the prototype that is still required for the function call, then the only way to do it is to set up a function call and put the value variable you want returned equal to the sub- procedure name and parms.
So, given that, let’s look at both the calling and the called program to see what changes are required to make the return values and function call work. First, the calling program.
Calling Program
Let’s start this process by taking a look at the program where the function call is issued and see what changes have to be made to accommodate it and the return value.
And now, once again, this time dissecting the program. Remember, this is the program that calls the sub-procedure held in the service program.
First, we don’t need an H-spec to make this program ILE (because we will need to compile it using CRTRPGMOD since it is calling a service program, although we could use one to set the activation group) so we go right to the F-spec for the display file where I have defined the PRDNO value. And again, I have not included the logic to actually pick up that value in an effort to keep this simple.
Then come the D-specs, which includes the PR. You might be tempted to think that with return values and a function call you wouldn’t need the PR, but that is not true. You always prototype to access another module in ILE. In this case, there is one subfield under that PR because that is how the sub-procedure is written. The D01_PRDNO value will be returned automatically if it is changed in the sub-procedure. Note that DESCP is not referenced at all in the prototype; return values variables are not part of the PR D-spec.
What is different is that the VAL_PRDNO PR D-spec has a length of 30 on it. Had you noticed that? So far we have not put a length on the PR line and with good reason. According to the Treaty of Versailles, the international symbol for “I got a return value coming back on this puppy” is a length on the PR line.
This is not the overall length of the parms in the PR (that is only 15) but rather the size of the field we want to get back in the return value (DESCP). You need to put that length here so that a pipeline is established to get that value returned.
Without it, the return values and the function call will fail in the compile.
Then the logic statements. Notice that instead of a CALLP to VAL_PRDNO(D01_PRDNO), we are using an evaluate statement. I have put the CALLP in there and commented it out just so you can see it.
When the sub-procedure is accessed, the value for the DESCP field will then be returned to the DESCP field in this program.
Soooooo, to summarize, in the calling program we need to:
- Put the length of the return values field on the PR, and
- Use the function call, setting it equal to the field we want passed back.
- Then, that field will not appear directly in the prototype
Called Program
And now, on to the called program. I have decided to make the call to a service program. Remember, it could also have been done to a sub-procedure that is embedded in the calling program. It cannot be done to an independent program (that call has to be done via a CALLP).
Seriously, I am sorry if I am belaboring some of this, but I want you to get very familiar with looking at sub-procedures and service programs. The more you do that, the less likely you will be to fear and detest them, and the more likely you will be to consider them friends and compatriots in your fight against uh, whatever you are fighting against. Yourself most likely, I would guess.
We start with the standard NOMAIN that is required for every service program.
Followed by the F-specs for the file we will use to do the validation. Remember, if you are on 6.1 or above, this F-spec could go in the actual sub-procedure.
Then the global D-spec for the prototype (the PR). If we had put this in with a copybook, then we would be using the COPY statement that is commented out. Note that we set a length for the PR line (30), the length of the field we are going to be passing back via the return value. This is required. We will see the same thing on the PI below because they must match.
What you might have not noticed is while PRDNO is the same on both the PR and PI here, we actually named the subfield D01_PRDNO in the calling program. Is this OK? Yes, it is. The names must match within a module, but they can be different between the calling and called programs. Not sure why you would want to do this, but it could come up. Anyway, carry on.
Then the start of the sub-procedure. We include the EXPORT keyword because we are calling this sub-procedure outside of this service program. We will talk more about this keyword and exporting later.
Followed by the D-specs for the sub-procedure including the prototype interface (PI). Again, please notice the length on the PI line indicating that a return value is to be set. Remember, the PR D-spec that we had outside of the P-spec could just as well have been set up inside here. And, if this service program had other sub-procedures that had different prototype subfields beyond just PRDNO, we would have had to set it up in here because a single PR would not work.
Finally, we get to the logic. This example will be different from the ones we have used so far because the RETURN opcode is used only for specific situations. In this case, we are going to pick up a field from the MSPMP100 record that we read— here it’s the description (DESCP)—and return that. If no record exists, we return a -1, which is sort of standard for function calls that fail. Note that PM_DESCP is not specified in a PR or PI, but it is defined in the F-spec for that file.
If you indicate you are going to do a return value (by putting a spec on the PI line), then you must actually execute the RETURN opcode (so that is why we have two branches on the IF above), or else an exception will be issued.
And then finally, the end to the sub-procedure and, in this case, the service program.
And One More Thing
There is one more thing that I should mention, and that is that you can use the function call without having any return values.
In other words, so far we have used the function call in concert with a return value, so that the code looked like:
DESCP = VAL_PRDNO(D01_PRDNO);
But if there is no return value that you want to have sent back, if you’re just concerned with the call and its associated parms, you can dispense with the return value, and the result looks like this:
VAL_PRDNO(D01_PRDNO);
This format is wildly popular with a lot of people. It does look Web-like, and I have nothing really against it except, as I have said before, I used the CALLP as a visual clue that I was calling a module at this point in the program. But it’s pretty obvious that you are doing that. I guess. Anyway, expect to see this format a lot in the future.
Does It Matter?
Does it matter whether you use a CALLP or the evaluate method to access the sub- procedure? Well, like most things, the answer is not clear-cut.
I have seen a number of articles praising this option. Some people seem to use it almost exclusively. And there might be times when you want to take advantage of it.
For example, the word on the street is that using the function prototype is more efficient than using CALLP. I am sure it is true. And it is also probably true that in most cases, that is not going to make a whole lot of difference in your response time. But if you are doing a call many, many times in your program, it might make really good sense to use the function call instead of the CALLP.
Plus, the function call-return value approach is a very Web-looking thing. That is the way it is done in PHP, and you know what I think about that. (Actually, I wish PHP had a call function like CALLP, but let’s not go there.) Of course, if PHP is going to jump off a bridge, does that mean you are going to do it, too?
But it is possible that being able to say that we can do this kind of thing in RPG is one step to making our language look a little less stuffy to Web types. And perception is important.
But on the whole, for me, there are a number of things that make me say that for general use, it wouldn’t be my choice.
First, it doesn’t allow you to eliminate the PR and PI stuff. If it did, then that would be a real plus (maybe), but it doesn’t.
Second, it doesn’t allow you to return more than one parm. Granted, you could return a data structure and then break that down, but that is extra steps and extra code on top of the already-existing PR.
Nor do you want to think of this as an emergency way to get anything you want out of the sub-procedure without changing the parms.
That is, today you need the DESCP returned (which is a 30-character text), and tomorrow you will need the opening inventory balance for this month (which is an 11,3 packed thingy). You can’t get both by using the same sub-procedure with return values because you have to put the parm length at the PR and PI top level in the sub-procedure. I suppose you could frog around with a default length on the PR and PI, then convert it when you got back to the calling program, but even then you would have to set it up within the sub-procedure, and that would require changes.
Plus, while the return values field is not in the prototype D-specs, you do reference the field name in both the calling and the called program, meaning you would need to modify them both if you changed the field you were having returned.
Fourth, and this is a big thing for me, it sort of hides the call. If a module is going to access another one, I like something that really stands out, like a CALLP. I agree, I still have the PR in my calling program, but using the eval versus a CALLP just hides it a bit as to where the access is occurring. I know a lot of people don’t have that hang-up, but what can you do? I am what I am.
In the end, the function call-return values combo is a valid alternative to the CALLP. However, except for the efficiency improvement, there are not enough positives to recommend it to me as my go-to access method. It’s OK, though, if you feel differently. What’s important, even if you don’t use it yourself, is that you can recognize it and understand how it works.
What Ya Shoulda Learned
In the end, there is a fair amount of stuff here, lots of rules, but there are really only a few things you want to make sure that you memorize. Other things can be looked up as needed.
- First, understand the relationship between function calls and return That is, they are like a Reese’s Peanut Butter Cup: you don’t get one without the other (chocolate and peanut butter—gee, do I have to explain everything?).
- Second, you need to know that you can only do the RETURN on one So if you need two fields returned, this approach by itself won’t work (it results in a compile error).
- Third, you should have learned how to set up the caller and sub- procedure to return a variable other than a
- You should also know how to set up a call to a sub-procedure as a function rather than by using the CALLP.
I am assuming there are other things you need to know, but I cannot relate any of them to function calls or return values.
Next time: The Importance of BIFs? You can pick up Dave Shirey's book, 21st Century RPG: /Free, ILE, and MVC, at the MC Press Bookstore Today!
LATEST COMMENTS
MC Press Online