24
Susan Gantner Susan.Gantner @ Partner400.com www.Partner400.com www.SystemiDeveloper.com Subroutines to Subprocedures & Service Programs, Part 1 Susan doesn’t code subroutines any more. She believes that subprocedures (aka, procedures) make great subroutine replacements. They can make your code more obvious, making maintenance easier, faster and more reliable. Take the extra step to package your commonly used procedures into ILE Service Programs and you can share them easily and efficiently among many programs. In this 2 part session, Susan will explain the advantages of modularizing your code using subprocedures and she'll cover how to do it. Part 1 covers the details of coding subprocedures - both the syntax and the best practices. We'll look at how prototypes are used with subprocedures and cover some valuable prototype keywords to make your coding life easier. Part 2 assumes you have either attended Part 1 or already have experience coding internal subprocedures, using local data and prototypes. In this second session, we'll cover the details of ILE Service Programs - what they are, why you should use them, how to package your subprocedures in them, and shortcuts for using them from your other programs. These two sessions can form a foundation for writing better modular applications. Together with her partner, Jon Paris, Susan authors regular technical articles for IBM Systems Magazine, IBM i edition, and the companion electronic newsletter, IBM i Extra. You may view articles in current and past issues and/or subscribe to the free newsletter or the magazine at: http:// www.IBMSystemsMag.com/ibmi Jon and Susan also have a blog on this site. Susan and Jon are also partners in SystemiDeveloper, hosts of the RPG &DB2 Summit conferences. See SystemiDeveloper.com for more details. This presentation may contain small code examples that are furnished as simple examples to provide an illustration. These examples have not been thoroughly tested under all conditions. We therefore, cannot guarantee or imply reliability, serviceability, or function of these programs. All code examples contained herein are provided to you "as is". THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE EXPRESSLY DISCLAIMED. © Copyright Partner400, 2004-2016. Page 1

Subroutines to Subprocedures & Service Programs, … source members can contain multiple subprocedures § Subprocedures can use other Subprocedures - But they cannot be nested When

Embed Size (px)

Citation preview

Page 1: Subroutines to Subprocedures & Service Programs, … source members can contain multiple subprocedures § Subprocedures can use other Subprocedures - But they cannot be nested When

Susan Gantner

Susan.Gantner @ Partner400.com www.Partner400.com www.SystemiDeveloper.com

Subroutines to Subprocedures & Service Programs, Part 1

Susan doesn’t code subroutines any more. She believes that subprocedures (aka, procedures) make great subroutine replacements. They can make your code more obvious, making maintenance easier, faster and more reliable. Take the extra step to package your commonly used procedures into ILE Service Programs and you can share them easily and efficiently among many programs.

In this 2 part session, Susan will explain the advantages of modularizing your code using subprocedures and she'll cover how to do it.

Part 1 covers the details of coding subprocedures - both the syntax and the best practices. We'll look at how prototypes are used with subprocedures and cover some valuable prototype keywords to make your coding life easier.

Part 2 assumes you have either attended Part 1 or already have experience coding internal subprocedures, using local data and prototypes. In this second session, we'll cover the details of ILE Service Programs - what they are, why you should use them, how to package your subprocedures in them, and shortcuts for using them from your other programs.

These two sessions can form a foundation for writing better modular applications.

Together with her partner, Jon Paris, Susan authors regular technical articles for IBM Systems Magazine, IBM i edition, and the companion electronic newsletter, IBM i Extra. You may view articles in current and past issues and/or subscribe to the free newsletter or the magazine at: http://www.IBMSystemsMag.com/ibmi Jon and Susan also have a blog on this site.

Susan and Jon are also partners in SystemiDeveloper, hosts of the RPG &DB2 Summit conferences. See SystemiDeveloper.com for more details.

This presentation may contain small code examples that are furnished as simple examples to provide an illustration. These examples have not been thoroughly tested under all conditions. We therefore, cannot guarantee or imply reliability, serviceability, or function of these programs.

All code examples contained herein are provided to you "as is". THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE EXPRESSLY DISCLAIMED.

© Copyright Partner400, 2004-2016.

Page 1

Page 2: Subroutines to Subprocedures & Service Programs, … source members can contain multiple subprocedures § Subprocedures can use other Subprocedures - But they cannot be nested When

Subroutines to (internal) SubproceduresThe basics § Converting a subroutine § Returning a value § Basic prototype options

Data § Local vs. Global § Static vs. Automatic

Creating a new procedure Compiling pgms with subprocedures

Coming up in Part 2 (not in this session): § Creating ILE modules & Service Programs § Externalizing our subprocedures using a Service Program

This is an agenda for this first part of the 2-part session.

In this session we will introduce you to the basics of writing subprocedures in RPG IV

We will start by showing how a simple subroutine can be converted to a subprocedure.

Next we will go on to discuss how subprocedures can be used in Service Programs.

Finally we will look at some of the additional prototyping options available for use with subprocedures.

As we hope to convince you in this section, Subprocedures are probably the biggest single change ever in the history of RPG. They open up whole new ways of building your applications.

© Copyright Partner400, 2004-2016.

Page 2

Page 3: Subroutines to Subprocedures & Service Programs, … source members can contain multiple subprocedures § Subprocedures can use other Subprocedures - But they cannot be nested When

What's a Subprocedure?A cross between a subroutine and a program § Like a subroutine

- It may be coded in the same source member where it is used - Follows all mainline logic - Compiled with its caller - Performance level similar to a subroutine

§ Like a program - Contains its own data - Data declarations and Files - Accepts parameters - May be compiled separately from its caller to ease reuse

Note that a subprocedure may be coded in (and compiled with) its caller - more like a subroutine.

However, it may instead be coded and compiled separately from its caller. In this case, you would use ILE static binding facilities (most likely a Service Program) to bind the caller to the called subprocedure. This externalizes the routine and makes it easily shared among many programs without duplicating the code - either at the source level or at the compiled level.

© Copyright Partner400, 2004-2016.

Page 3

Page 4: Subroutines to Subprocedures & Service Programs, … source members can contain multiple subprocedures § Subprocedures can use other Subprocedures - But they cannot be nested When

Subprocedures can: § Define their own Local variables & files

- This provides for "safer" development since only the code associated with the variable can change its content

- More on this later § Access files and variables defined in the Global section

- i.e. in the main body of the source, shared with other code § Be called recursively

In other languages it would be called Function or Procedure § If it returns a value it is a Function

- Much like an RPG Built-In Function (BIF) § If it does not return a value it is a Procedure

- It is simply called with a CALLP and "does stuff" for you like a *PGM call

Subprocedures

If %EOF(MyFile) = *On;

If ValidCustomer(CustNo);

Support for subprocedures was added to the RPG IV language in releases V3R2 and V3R6. User written subprocedures in RPG IV allow for recursion (i.e., the ability for the same procedure to be called more than once within a job stream). They also allow for true local variable support (i.e., the ability to define fields within a subprocedure that are only seen by and affected by logic within the bounds of the subprocedure.) RPG IV subprocedures use prototypes, which are a way of defining the interface to a called program or procedure. In this session, we will concentrate on writing and using RPG IV subprocedures, but you will find that many of the same prototype-writing skills can be applied to access system APIs and C functions.

The code sample here is intended to show the similarities between calling an RPG built-in function and one of your own functions that is written as a subprocedure.

Note that although a CALLP is used to invoke subprocedures that do not return a value, you should not be mislead into thinking that CALLP means CALL Procedure. It does not - it actually stands for CALL with Prototype.

Most of the time your subprocedures will return a value, but sometimes you'll just want to have it "do stuff". For example a subprocedure named WriteErrorLog might be used to record errors detected by the program. There's not a lot of point in having it return a value to say it did it - after all what are you going to do if it couldn't? Call it again to write another error message? <grin>

© Copyright Partner400, 2004-2016.

Page 4

Page 5: Subroutines to Subprocedures & Service Programs, … source members can contain multiple subprocedures § Subprocedures can use other Subprocedures - But they cannot be nested When

RPG source members can contain multiple subprocedures § Subprocedures can use other Subprocedures

- But they cannot be nested

When they return a value they are used as functions § Just as if they were built into the language like IBM's BIFs § They have a data type and size

- You can use them anywhere in the freeform calcs where a variable of the same type can be used ✴ e.g. In any expression that can accept the data type it returns

A Subprocedure DayOfWeek can be used as shown below § We will be developing this routine later

Major Features of Subprocedures

If DayOfWeek(ADateFld) > 5; WeekDay = DayOfWeek(Today);

When we talk about the "Source Member" we really mean all of the RPG IV source that is processed by the RPG compiler in any one compilation. This would include any source members that were /COPY'd into the main source. You may wonder why we used the term "Source Member" rather than "Program". Traditionally we have tended to equate a source member to a program since the normal RPG/400 approach meant that one source was compiled and this resulted in a program (*PGM) object. With RPG IV each source is compiled into a module (*MODULE), and a number of modules may be combined into a single program.

Subprocedures which return a value are used very much like RPG IV built-in functions, as shown in the examples on this chart. In the example, "DayOfWeek" is the name of an RPG IV subprocedure. In fact we will be building this exact procedure shortly.

It requires a single date field as the input parameter (which in the first example is passed via the field named "ADateFld" and in the second example via the field "Today").

It returns a value, which is a number representing the day of the week (1 through 7). The returned value effectively replaces the function call in the statement. In the first example, that value will be compared with the literal 5. In the second example, the returned value will be placed in the field "WeekDay".

© Copyright Partner400, 2004-2016.

Page 5

Page 6: Subroutines to Subprocedures & Service Programs, … source members can contain multiple subprocedures § Subprocedures can use other Subprocedures - But they cannot be nested When

// Variables used by DayOfWeek subroutine

D AnySunday C D'04/02/1995' D WorkNum S 7 0 D WorkDay S 1S 0 D WorkDate S D

// Additional program logic omitted

/Free

WorkDate = InputDate; ExSr DayOfWeek; DayNumber = WorkDay;

// Additional program logic omitted

// Calculate day of week (Monday = 1, Tuesday = 2, etc.)

BegSr DayOfWeek;

WorkNum = %Diff( WorkDate : AnySunday: *D ); WorkDay = %Rem( WorkNum : 7 ); If WorkDay < 1; WorkDay = WorkDay + 7; EndIf; EndSr;

/End-Free;

DayOfWeek Subroutine

In this presentation, we will take the subroutine in this program and turn it into a subprocedure. Existing subroutines in programs often make good candidates for subprocedures.

Here we see the traditional use of a standard subroutine, along with all its inherent problems. WorkDate and WorkDay are "standard" field names within the subroutine that we have to use. In effect they are acting as parameters because the subroutine is used from many places in this program using different fields for the calculations (note that we have omitted all except the relevant code for this example).

We must move fields to/from these "standard" fields to in order to use the subroutine. Once we have turned this into a subprocedure, these additional steps will not be necessary.

The use of common subroutines also forces us to use naming standards to ensure that work fields within the subroutine do not get misused. This can certainly hinder attempts at producing meaningful field names - particularly with RPG III's six character limit!

© Copyright Partner400, 2004-2016.

Page 6

Page 7: Subroutines to Subprocedures & Service Programs, … source members can contain multiple subprocedures § Subprocedures can use other Subprocedures - But they cannot be nested When

Basic Subprocedure

D DayOfWeek PR 1S 0 D ADate D /FREE DayNumber = DayofWeek(InputDate); /END-FREE P DayOfWeek B

D DayOfWeek PI 1S 0 D WorkDate D

D WorkNum S 7 0 D WorkDay S 1S 0 D AnySunday C D'04/02/1995'

/FREE WorkNum = %Diff( WorkDate : AnySunday: *D ); WorkDay = %Rem( WorkNum : 7 );

If WorkDay < 1; WorkDay = WorkDay + 7; EndIf;

Return WorkDay;

/END-FREE P DayOfWeek E

3

2

1

4

5

V7.1+ Free form version on

next chart

This is the code for our completed subprocedure. This example uses fixed format D and P specs which are required prior to the V7.1+ enhancements. The next chart shows the same code using the new V7.1+ free format version of the code.

Notice the basic sequence of the specifications. The prototype(s) appear at the beginning of the source, along with any other D specs. The P specification that begins the subprocedure appears after the regular C specs. We will look in more detail at the sequence of specifications in this "new style" of RPG program later,

In this example, the stand-alone and constant fields are local variables available only to the logic in this particular subprocedure. More on what we mean by "local" later.

In case you haven't seen the use of the %REM built-in before, it returns the remainder of the result of dividing the first parameter by the second. It replaces a DIV followed by a MVR.

© Copyright Partner400, 2004-2016.

Page 7

Page 8: Subroutines to Subprocedures & Service Programs, … source members can contain multiple subprocedures § Subprocedures can use other Subprocedures - But they cannot be nested When

Basic Subprocedure - V7.1+ Free Form

Dcl-PR DayOfWeek Zoned(1); ADate Date; End-PR;

DayNumber = DayofWeek(InputDate);

Dcl-Proc DayOfWeek;

Dcl-PI DayOfWeek Zoned(1); WorkDate Date; End-PI;

Dcl-S WorkNum Packed(7); Dcl-S WorkDay Zoned(1); Dcl-C AnySunday Const( D'04/02/1995');

WorkNum = %Diff( WorkDate : AnySunday: *D ); WorkDay = %Rem( WorkNum : 7 );

If WorkDay < 1; WorkDay = WorkDay + 7; EndIf;

Return WorkDay;

End-Proc DayOfWeek;

3

2

1

4

5

Most remaining examples will use the D & P spec style

with free format shown in the notes.

In V7.1+ we could omit the PR

declaration here.

This is the code for our completed subprocedure - this version using the free format support for what formerly required D and P specs prior to V7.1+.

Note that we could have omitted the PR declaration in this example with V7.1+ because this is an internal subprocedure (for now, anyway). We left it in here for 2 reasons - first, to illustrate how the PR looks in free format and second because later we’ll be externalizing this subprocedure - in which case we will need the PR to be included in the callers of DayOfWeek.

Notice the basic sequence of the specifications. The prototype(s) (Dcl-PR) appears at the beginning of the source, along with any other data declarations. The Procedure declaration (Dcl-Proc) begins the subprocedure appears after the regular logic statements. We will look in more detail at the sequence of specifications in this "new style" of RPG program later,

In this example, the stand-alone and constant fields are local variables available only to the logic in this particular subprocedure. More on what we mean by "local" later.

In case you haven't seen the use of the %REM built-in before, it returns the remainder of the result of dividing the first parameter by the second. It replaces a DIV followed by a MVR.

© Copyright Partner400, 2004-2016.

Page 8

Page 9: Subroutines to Subprocedures & Service Programs, … source members can contain multiple subprocedures § Subprocedures can use other Subprocedures - But they cannot be nested When

Converting to a subprocedure allows us to use DayOfWeek as if it were a built-in function of RPG § It just doesn't have a % sign in front of the name!

The date to be processed (WorkDate) is passed as a parm § No need to 'fake' parameters as in the original

- More on parameter definition in a moment

Invoking the Subprocedure

DayNumber = DayofWeek(InputDate);

WorkDate = InputDate; ExSr DayOfWeek; DayNumber = WorkDay;

1

We call the subprocedure in much the same way as we use a built-in function in RPG. Since it returns a value, we can call it in an expression. The returned day number will be placed in the field called DayNumber. If our subprocedure did not return a value we would invoke it with a CALLP.

This chart illustrates one of the most important reasons to use subprocedures over subroutines.

The only way to tell what data is involved in the DayOfWeek subroutine is to look at the logic.

There is no way to tell, for example, what field contains the “input” to the DayOfWeek subroutine, nor is there any way to tell where the answer or result of the DayOfWeek logic goes. This can only be done by studying the logic of the subroutine.

But in the call to the DayOfWeek subprocedure, it is obvious that the input to the routine is the InputDate field and that the result of calling the routine is placed in the DayNumber field. So in many cases, it will no longer be necessary to study the logic of DayOfWeek. We know what it does (by its name) and what it does it to (by the input parms) and what the result of the routine is. Unless the change needed is related to how the day of the week was calculated, there is no need to look at the code - and every programmer who maintains this code in the future will avoid spending that extra time trying figure that out.

© Copyright Partner400, 2004-2016.

Page 9

Page 10: Subroutines to Subprocedures & Service Programs, … source members can contain multiple subprocedures § Subprocedures can use other Subprocedures - But they cannot be nested When

Procedures are bounded by P-specs § A type B(egin) names the procedure § A type E(nd) is required to complete it

A Procedure Interface (PI) is also required § The PI line defines the Data type and Size of the procedure

- The procedure can be used anywhere that a field of the same type and size can be used

§ Subsequent lines define any parameters - i.e. The PI acts as the procedure's *ENTRY PLIST

P-specs & the Procedure Interface

P DayOfWeek B

D DayOfWeek PI 1S 0 D WorkDate D : : : P DayOfWeek E

2

The procedure name is optional on both the PI and the E(nd) P-Spec

V7.1+ Free format version shown in the notes

Subprocedures begin and end with P specs (or Dcl-Proc/End-Proc). In this example, we see the beginning and ending P specs. The beginning P spec contains a B in the position that would contain, for example, a DS on a D spec. The P spec has a very similar layout to the D spec.

The next thing we need is a Procedure Interface, or PI. The procedure interface defines the interface to the procedure -- most significantly, the parameters passed to and/or from the subprocedure. The PI is defined on the D specs (or between Dcl-PI and End-PI statements), typically as the first data declarations in the subprocedure. The PI replaces the need for a *ENTRY PLIST for the subprocedure.

The data item defined on the same line as the PI is the return value. (Note: It is possible to have a subprocedure that returns NO value. A subprocedure can return, at most, one value.)

The data item(s) that follow the PI with nothing specified in the column where the PI is (on fixed format D specs) or the statements before the End-PI (in free form) - in this example, the WorkDate field - is/are the input parameters into the subprocedure.

The data items following the PI and its parameters are all local fields to the subprocedure. On a D spec, you can tell the end of the procedure interface and by the appearance of something (in this example, an "S") in the column where PI was specified.

Free format version of the code in the example. If we had omitted the procedure name on the Dcl-PI we would need to specify *N as a name placeholder - e.g., Dcl-PI*NZoned(1);

Dcl-ProcDayOfWeek;

Dcl-PIDayOfWeekZoned(1);WorkDateDate;End-PI;

End-ProcDayOfWeek;

© Copyright Partner400, 2004-2016.

Page 10

Page 11: Subroutines to Subprocedures & Service Programs, … source members can contain multiple subprocedures § Subprocedures can use other Subprocedures - But they cannot be nested When

Subprocedures allow us to define local variables § They can ONLY be referenced within the subprocedure

Much safer than the traditional RPG approach § The writer of the procedure can protect all of the variables

- Only those that should be changed can be changed !

Local Data Definition

P DayOfWeek B

D DayOfWeek PI 1S 0 D WorkDate D

D WorkNum S 7 0 D WorkDay S 1S 0 D AnySunday C D'04/02/1995'

3

V7.1+ Free format version shown in the notes

The local data definitions are shown here in the darker font. The lighter font portions we’ve discussed already and are included only to show the context or relative location of the local field definitions.

Local data is very important for reusable procedure logic. It is also important to make maintenance tasks more reliable, as there is more independence within the subprocedures and, therefore, less chance that maintenance tasks will inadvertently affect mainline logic. In this example, the fields AnySunday, WorkNum and WorkDay are local fields to this subprocedure. They can only be referenced from the subprocedure.

We'll take a closer look at Local data later in the presentation.

Dcl-ProcDayOfWeek;

Dcl-PiDayOfWeekZoned(1);WorkDateDate;End-Pi;

Dcl-SWorkNumPacked(7);Dcl-SWorkDayZoned(1);Dcl-CAnySundayConst(D'04/02/1995');

© Copyright Partner400, 2004-2016.

Page 11

Page 12: Subroutines to Subprocedures & Service Programs, … source members can contain multiple subprocedures § Subprocedures can use other Subprocedures - But they cannot be nested When

RETURN is used to send the result back to the caller § It can simply return a value as in our original version

Or it can return an expression as shown below

Often a procedure consists simply of a RETURN op-code § For example, the entire logic for a procedure called CalcItemTotal

which took the parameters Price, Discount and Quantity might be: § Return ( Price * ( Discount / 100) ) * Quantity;

Returning the result 4

Return WorkDay;

If WorkDay < 1; Return WorkDay + 7; Else; Return WorkDay; EndIf;

Here we specify the return value for this subprocedure. We use the RETURN operation code and specify the return value in factor 2. Notice that the RETURN operation code is now a freeform operation.

The returned value can be either a single field or an expression, as in the second example. In fact, since a subprocedure can be used anywhere a variable of its type can be used, the returned value itself could be the result of a subprocedure invocation. But we're getting a little deep a little too quickly here ....

© Copyright Partner400, 2004-2016.

Page 12

Page 13: Subroutines to Subprocedures & Service Programs, … source members can contain multiple subprocedures § Subprocedures can use other Subprocedures - But they cannot be nested When

Each procedure requires a Prototype § Notice that it's almost identical to the PI § It must be present when compiling the procedure**

- This allows it to be validated against the Procedure Interface § It is also required in each program that wants to use the procedure If the procedure is externalized (we'll get to this later) § The preferred approach is to code it as a /Copy member § Place Prototypes for groups of related functions in a single

member - Possibly one member per Service Program

Prototypes simply provide information to the compiler § They don't result in any data definition

Defining the PRototype 5

D DayOfWeek PR 1S 0 D ADate D

Dcl-PR DayOfWeek Zoned(1); ADate Date; End-PR;

**RPG 7.1 Update: Prototypes are optional in

some circumstances

Fixed form Prototype

Free form Prototype V7+

The next step is to define the prototype. The parameters in the prototype must match the Procedure Interface (PI) because it also defines the interface to the procedure. The prototype will be used by the procedures that call this subprocedure.

The prototype is also required to be placed into the module where the procedure is defined. This is so the compiler can check the validity of the prototype -- that is, that the parameters specified match the Procedure Interface in terms of number and type. Note that beginning with 7.1, the rules for placement of prototypes have been relaxed. The compiler no longer requires prototypes for internal subprocedures, like the one we have here. Prior to 7.1, they are required and are probably a good idea to have, generally speaking.

At this point in the development of the subprocedure, we are hard coding the prototype in the main line portion of the program. Later when we decide to externalize the subprocedure, its prototype will be moved to a /COPY source member. This is a common (and encouraged) practice. The prototypes for subprocedures are grouped in a separate source member that is copied in (via the /COPY directive). This is especially important if the subprocedure is placed in a separate module (source member) because it is critical that the prototype in the calling procedure match the one in the defining procedure, since it is the once in the module containing the subprocedure that the compiler verified for you.

In the event that the subprocedure is placed in the same source member as the caller (as in our basic example), then only a single copy of the prototype is required (until 7.1, when the prototype becomes optional), because the compiler will be able to check the prototype and procedure interface in a single compile step. If the subprocedure were to be placed in a separate source member, then a copy of the prototype would be required in both the member containing the subprocedure and in the main procedure (or calling procedure). as well as in any other main or subprocedures calling this subprocedure.

© Copyright Partner400, 2004-2016.

Page 13

Page 14: Subroutines to Subprocedures & Service Programs, … source members can contain multiple subprocedures § Subprocedures can use other Subprocedures - But they cannot be nested When

If the date routines are to be used against multiple dates § What if the formats for the date parameters are of different formats?

- We should specify the format to make sure the subprocedure always gets the format it wants

§ Especially important in the next session when we externalize these subprocedures

So ALWAYS specify a format § When using dates as parms OR return values

Improving Our Prototypes

// Fixed format Prototype for DayOfWeek procedure

D DayOfWeek PR 1 0

D InputDate D DATFMT(*USA)

// Free format Prototype for DayOfWeek procedure

Dcl-PR DayOfWeek Zoned(1); InputDate Date(*USA); End-PR;

The DATFMT keyword should always be specified for a date field returned by a subprocedure, or passed as a parameter. In other words any date field defined within a Procedure Interface (PI) or Prototype (PR) should have the DATFMT keyword.

Why? Well without it, the format of the date is determined at the time of compilation. Without the DATFMT keyword, the date will be classified as the default type for the compilation. While this is normally *ISO, it is subject to change at the whim of an H-spec DATFMT entry.

Suppose that our prototype does not specify the date format. Later, after the externalize these subprocedures, then when we /COPY it into a program with no H-spec, the compiler will interpret it as an *ISO date. If the program then passes an *ISO date to the subprocedure, the compiler will accept this as valid.

Now suppose that in the source member that contains our subprocedure, we have an H-spec that specifies the default format as being *USA. The same /COPY member will now have it's date fields interpreted as being *USA dates.

The result would be that within the subprocedure the date is viewed as *USA while in the calling program it is defaulting to *ISO. The prototype that should protect us from such problems is allowing a date of the wrong format to be passed. Not a recipe for success!!

Note that although we have emphasized this as being an issue with dates, the same holds true for Times. It is not and issue with Timestamps since they only have one format.

© Copyright Partner400, 2004-2016.

Page 14

Page 15: Subroutines to Subprocedures & Service Programs, … source members can contain multiple subprocedures § Subprocedures can use other Subprocedures - But they cannot be nested When

Now that we’ve specified DATFMT § The compiler will reject any attempt use any other format

The CONST keyword can help us here § It allows the compiler to accept a date in any format

- The compiler generates a temporary field in the correct format and moves the parameter into it before passing the copy

§ This is helpful not only with dates - what about packed vs. zoned? § What if an expression or hard-coded constant value is passed?

- For example: DayPrint = DayOfWeek( %Date(NumDate:*YMD) ); § Remember: Both the prototype & the procedure interface must

change (PR & PI must match)

CONST (Constant) Parameters

D DayOfWeek PR 1 0 D InputDate D CONST DATFMT(*USA)

Dcl-PR DayOfWeek Zoned(1); InputDate Date(*USA) Const; End-PR;

In this example, by combining CONST and the date format, the compiler can generate a temporary (hidden) date field in the calling program or procedure, if necessary, in order to convert the date format used by the caller to the format (in this case, *USA) used in the called subprocedure.

In general, the use of the CONST keyword allows the compiler to accommodate mismatches in definitions of parameters between the calling and called programs or procedures. When you use this keyword, you are specifying that it is acceptable that the compiler to accommodate such mismatches by making a copy of the parameter (if necessary) prior to passing it.

Note that CONST indicates that the parameter is Read-only i.e. the called subprocedure cannot change its value. The compiler will in fact defend against this and will error out any attempts to change the value of parms passed as CONST.

There is no equivalent to CONST needed on the definition of the returned value. If the compiler notes that (for example) and *ISO date is being returned and it being assigned to a *USA date field, it will automatically convert the date just as it would in a simple assignment of one date type to another.

It is important to realize that CONST is not only helpful for date parameters. All parameters that are not going to be changed by the called subprocedure can and should be passed as constants (i.e., specifying CONST). That way, the caller can pass constants, expressions, other numeric data types (packed vs zoned), etc. It makes the subprocedure far more useful in different circumstances.

Granted, this detail is perhaps more important after the next session when we make our subprocedure available externally. But it is still a good habit to get into using even with internal subprocedures.

© Copyright Partner400, 2004-2016.

Page 15

Page 16: Subroutines to Subprocedures & Service Programs, … source members can contain multiple subprocedures § Subprocedures can use other Subprocedures - But they cannot be nested When

D Temp S 40A

LocalValue = 0;

Temp = 'Temp in Procedure2';

"Local" and "Global" Variables

D LocalValue S 7P 2 Inz D Temp S 7P 2 Inz

Count = Count + 1;

LocalValue = 0;

Temp = LocalValue;

D Count S 5P 0 Inz D Temp S 20A

Count = Count + 1;

LocalValue = 0;

Temp = 'Temp in Main';

Main Program Code

Procedure1

Procedure2

All code in 1 Source Member

Any and all subprocedures coded within the source member will automatically have access to global data items defined in the main procedure. They can also define their own local data fields, which will be accessible only within the subprocedure where they are defined.

As a rule of thumb, use of global data within a subprocedure should be avoided whenever possible. Ideally, subprocedures should act upon the data passed in through parameters and affect the data back in the calling code only by returning a result. This avoids the possibility of side-effects where (by accident or design) a subprocedure unexpectedly changes the content of a field back in the calling code.

In addition to returning values, subprocedures can also modify parameters passed to them, just as a parameter on a conventional program call can be modified. However, this is not the preferred approach, for the same basic reasons that we stated for global data items.

In some circumstances accessing global data cannot be avoided. For example, although files can be used within a subprocedure, until V6R1, they can only be defined at the global level. Therefore in order to access the file data we must reference those global items.

Beginning with V6R1, F specs can also be located inside a subprocedure and in that case, the file is local to the subprocedure. When using a local file definition in a subprocedure, the I/O to that file must be via a local Data Structure, most likely defined using the LIKEREC keyword.

© Copyright Partner400, 2004-2016.

Page 16

Page 17: Subroutines to Subprocedures & Service Programs, … source members can contain multiple subprocedures § Subprocedures can use other Subprocedures - But they cannot be nested When

Local data fields use automatic storage by default § Can be overridden with the STATIC keyword on the D spec

- In other words, local data can be either static or automatic ✴ Global data is always static

What's automatic storage? § Storage that exists only for the duration of the procedure call § Goes away (is de-allocated) when procedure returns to its caller

Static storage is the only type for global fields in RPG § LR indicator controls when global fields are reinitialized § Local fields - even if declared STATIC - are not reinitialized after LR

Why use automatic storage? § Automatic "clean up" of fields without the overhead of LR § More efficient - storage not used unless / until procedure is called § It allows for recursive calls

Static vs. Automatic Storage

Data stored in automatic storage goes away when the procedure returns to its caller. Upon subsequent calls to the procedure, automatic fields "lose" their values. Data stored in static storage remains between calls to the procedure until the Activation Group where the procedure is activated is reclaimed.

Recursion (the ability for a subprocedure to be called multiple times in the same invocation stack -- in fact, for a subprocedure to call itself!) is made possible because of automatic storage. Each call to the subprocedure gets a "fresh" set of local automatic storage fields. This is why subprocedures may be called recursively, but main RPG procedures cannot.

Automatic storage can be considerably more efficient than static storage since it is only allocated if and when the subprocedure is called. Think about static binding and the fact that many procedures are often activated at once in a job! Remember that all the procedures in all the Service Programs referenced (directly or indirectly) by an ILE program are allocated immediately on the first call in the job to that ILE program. If that represents a lot of procedures (either main procedures or subprocedures) all static fields will be allocated and initialized by the system right away. In many cases, many of those procedures may never be called in this particular job. However, their static storage must always be allocated and remain allocated until the program's Activation Group is reclaimed. Use of automatic storage reduces this potential "overuse" of memory by allocating only what is needed and only for the duration that it is needed. In "memory-heavy" applications, this could have a noticeable impact on application performance and system efficiency.

© Copyright Partner400, 2004-2016.

Page 17

Page 18: Subroutines to Subprocedures & Service Programs, … source members can contain multiple subprocedures § Subprocedures can use other Subprocedures - But they cannot be nested When

H DftActGrp(*NO) ActGrp('QILE')

D ProcAuto PR D GlobalCount S 3 0 D X S

For x = 1 to 5; ProcAuto(); EndFor;

*INLR = *On;

P ProcAuto B D PI D CountStat S 3 0 Static D CountAuto S 3 0

CountStat = CountStat + 1; CountAuto = CountAuto + 1; GlobalCount = GlobalCount + 1;

Dsply 'CountStat=' ' ' CountStat; Dsply 'CountAuto=' ' ' CountAuto; Dsply 'GlobalCount=' ' ' GlobalCount;

P E

Example: Static & Automatic Storage

What 3 values are displayed

each time?

To test if you really understand automatic and static storage, can you predict what values will be displayed when the main procedure in this sample code is called?

The values of CountStat and GlobalCount increase by 1 each time they are displayed.

The value of CountAuto never increases. It is displayed as 1 every time!

Now, for the tougher question:

Assume this main procedure was immediately called a second time in the same job. What value would be displayed for CountStat and GlobalCount on the first call to the ProcAuto subprocedure ??

© Copyright Partner400, 2004-2016.

Page 18

Page 19: Subroutines to Subprocedures & Service Programs, … source members can contain multiple subprocedures § Subprocedures can use other Subprocedures - But they cannot be nested When

RPG IV Specification SequenceH Ctl-Opt Keyword NOMAIN required if no main line calc specsF Dcl-F File Specifications - Global D...PR Dcl-PR / End-PR Prototypes for all procedures used and/or defined in the

source (Often present in the form of a /COPY)D Dcl-xx Data definitions - GLOBALI * none* GLOBALC <free format> Main calculations (Any subroutines are local)O * none * GLOBAL

P...B Dcl-Proc Start of first procedureF Dcl-F Local File Specifications - only allowed V6R1+D...PI Dcl-PI / End-PI Procedure InterfaceD Dcl-xx Data definitions - LOCALC <free format> Procedure calcs (Any subroutines are local)P...E End-Proc End of first procedure

P...B Dcl-Proc Start of next procedure..... Procedure interface, D-specs, C-Specs, etc.P...E End-Proc End of procedure** ** Compile time data

This chart illustrates the layout of a complete RPG program containing one or more subprocedures.

Note that the NOMAIN keyword on the H specification or Ctl-Opt is optional and indicates that there is no mainline logic in this module, i.e., no logic outside the subprocedure logic. Note also that until V6R1, any file declarations always go at the top of the member, for any files that will be accessed, either by the mainline code (if any) or by the subprocedures. This is true regardless of whether there is any mainline logic or not.

A significant V6R1 enhancement allows files to be defined inside a subprocedure, in which case, the file becomes local to the subprocedure.

The first data declarations are the PR(ototypes) for any subprocedures that are going to be used or defined in this source member. It is not compulsory to have them first, but since you will not need to reference or change them very often it is a good idea to have them near the top. Of course any prototypes for subprocedures that you are used in multiple programs should be in a /COPY member and not cloned from program to program. The data declarations before the first procedure and any I or O specs in the program are for data items in the mainline, which are global, i.e., they can be accessed from both mainline logic and any subprocedures in this module.

Following the O specs (if any) for the mainline code is the beginning P spec for the first subprocedure. It is followed by the PI (procedure interface) for that subprocedure. Note that as of V6R1, local file definitions may exist in subprocedures. Other data declarations and logic for this subprocedure are next, followed by the end of the procedure, using the P spec or End-Proc.

Any other subprocedures would follow this, each with its own pair of beginning and ending P specs or Dcl-Proc / End-Proc.

© Copyright Partner400, 2004-2016.

Page 19

Page 20: Subroutines to Subprocedures & Service Programs, … source members can contain multiple subprocedures § Subprocedures can use other Subprocedures - But they cannot be nested When

How about a procedure to provide the day name § e.g., "Monday", "Tuesday", etc. for a specified date ? § This procedure will use DayOfWeek to obtain the actual day number

Procedures using Procedures

P DayName B D PI 9 D InpDate D DatFmt(*USA) Const

D DayData DS D 9 Inz('Monday') D 9 Inz('Tuesday') D 9 Inz('Wednesday') D 9 Inz('Thursday') D 9 Inz('Friday') D 9 Inz('Saturday') D 9 Inz('Sunday')

D DayArray 9 Overlay(DayData) Dim(7)

D WorkDay S 1 0 Inz /Free WorkDay = DayOfWeek(InpDate); Return DayArray(WorkDay); /End-Free P DayName E

Prototype for DayName is shown on notes page.

Free form version on next chart

This chart illustrates how subprocedures can use other subprocedures.

Note the technique to put the 7 values for the names of the days of the week into the array. We're using the ability to overlay the array on the Data Structure to "fill" the array with values. This is a better way than using compile time data to fill the array because it doesn't require us to look elsewhere to find the data going into the array. Also, compile time arrays and tables cannot be defined in subprocedures, so we cannot define our array that way if we want it to be local to the subprocedure.

The important point to notice here is that we have NOT cloned the logic of the DayOfWeek procedure. Instead we are truly re-using the logic of DayOfWeek by _calling_ DayOfWeek.

The new "DayName" procedure will call the "DayofWeek" procedure to get the number of the day of the week. The "DayName" procedure then translates the number into a day name.

Note that we must include 2 prototypes: one for DayName and one for DayofWeek, since DayName calls DayofWeek. It is important to note that ALL prototypes go up in the main part of the program's data declarations - never inside the subprocedure itself. Even though in this case, DayName calls DayOfWeek, both the DayOfWeek and DayName prototypes are placed together at the top of the source member with the global data declarations.

D DayOfWeek PR 1S 0 D InputDate D DatFmt(*USA) D Const

D DayName PR 9A D WorkDate D DatFmt(*USA) D Const

© Copyright Partner400, 2004-2016.

Page 20

Page 21: Subroutines to Subprocedures & Service Programs, … source members can contain multiple subprocedures § Subprocedures can use other Subprocedures - But they cannot be nested When

§ Note differences in data declarations ✴ Blank name replaced with *n & POS(1) replaces overlay to DS name

DayName - V7+ Free Form Version

Dcl-Proc DayName;

Dcl-PI DayName Char(9); InpDate Date(*USA) Const; End-pi;

Dcl-DS DayData; *n Char(9) inz('Monday'); *n Char(9) inz('Tuesday'); *n Char(9) inz('Wednesday'); *n Char(9) inz('Thursday'); *n Char(9) inz('Friday'); *n Char(9) inz('Saturday'); *n Char(9) inz('Sunday');

DayArray Char(9) Dim(7) Pos(1); End-ds;

Dcl-S WorkDay Packed(1);

WorkDay = DayOfWeek(InpDate); Return DayArray(WorkDay);

End-Proc DayName;

Prototype for DayName is shown on notes page.

The DayName subprocedure takes the date passed to it and passes it on to DayofWeek to get the numeric value, which is in turn used as an index to the array with the names of the days of the week.

Note a couple of syntax changes are required to replace the “ordinary” data declarations in this example. It doesn’t have much to do with the topic of subprocedures, but since the free form version does have a couple of notable syntactic differences, we decided to show this example.

Note that in free form, the name cannot be completely blank as it was on the D spec. A special value *N is used to designated an unnamed data structure subfield. Alternatively, of course, we could have simply supplied names for the subfields even though the subfields themselves will never be referenced. It seems more obvious to programmers following us to leave the names off.

Also note that when free format data declarations were introduced, they dis-allowed overlaying the data structure, although Overlay is still allowed to subfields in the DS. So the POS (position) keyword was introduced, partly so that we could say POS(1) in cases like this.

Note that we must include 2 prototypes: one for DayName and one for DayofWeek, since DayName calls DayofWeek. It is important to note that ALL prototypes go up in the main part of the program's data declarations - never inside the subprocedure itself. Even though in this case, DayName calls DayOfWeek, both the DayOfWeek and DayName prototypes are placed together at the top of the source member with the global data declarations.

Dcl-Pr DayOfWeek Zoned(1); InputDate Date(*USA) Const; End-Pr; Dcl-Pr DayName Char(9); WorkDate Date(*USA) Const; End-Pr;

© Copyright Partner400, 2004-2016.

Page 21

Page 22: Subroutines to Subprocedures & Service Programs, … source members can contain multiple subprocedures § Subprocedures can use other Subprocedures - But they cannot be nested When

There's nothing "wrong" with that approach but .... § Earlier we said that a subprocedure can be used anywhere that a

variable of its type can be used § So we can replace these three lines of code:

With this more streamlined version § Notice that we avoid the need to declare the 'WorkDay' variable ! § If you find this hard to understand - don't attempt to learn Java or

PHP or C#...

An alternative approach

Return DayArray(DayOfWeek(InpDate));

D WorkDay S 1 0 Inz

WorkDay = DayOfWeek(InpDate); Return DayArray(WorkDay);

Since subprocedures which return a value can be used anywhere a field name or constant of that type (i.e., alphanumeric or numeric) and size can be used, the second example you see here could be used to incorporate a "cleaner" programming style. After all, why create a field to hold the day number simply to use it as a subscript and then throw it away!

© Copyright Partner400, 2004-2016.

Page 22

Page 23: Subroutines to Subprocedures & Service Programs, … source members can contain multiple subprocedures § Subprocedures can use other Subprocedures - But they cannot be nested When

We have looked (so far) at creating internal subprocedures To compile these, you may use CRTBNDRPG (or 14 in PDM) § But, you must specify Default Activation Group *No

- DFTACTGRP(*NO) § This means you are creating a "real" ILE program

- The default (unless it has been changed on your system) is DFTACTGRP(*YES), which means create a *PGM that behaves like a non-ILE program

§ You can do it at compile time, but it's much better to include this keyword on your H spec - That way, you won't forget it next time!

§ Note that you could also compile using CRTRPGMOD - In that case, you must NOT include the DFTACTGRP keyword

Compiling with Subprocedures

H Option(*SrcStmt : *NoDebugIO) DftActGrp(*No)

Dcl-Opt Option(*SrcStmt : *NoDebugIO) DftActGrp(*No);

Fixed form

Free form

When you first try to create subprocedures you will probably meet the compiler message "RNF3788 - Keyword EXTPGM must be specified when DFTACTGRP(*YES) is specified on the CRTBNDRPG command". This occurs because the default values on the CRTBNDRPG command assume that the compilation is for an OPM style program (sometimes known as compatibility mode). Subprocedures are an integral part of the ILE environment and, if you are using them you are therefore using features of ILE, so the default value will not suffice.

To avoid this message, you must remember to compile with the option DFTACTGRP(*NO). Better still - embed it on an H-spec or Ctl-Opt in the source so you can't forget.

You may also want to include other keywords such as the ones shown here to make your runtime statement numbers match your source sequence numbers and to make debugging easier by creating only one "step" option for every I/O statement, such as CHAIN or READ.

Note that if you need to or prefer to compile your code using the CRTRPGMOD command (Create RPG Module) then you must not include the DFTACTGRP keyword since it is not valid for CRTRPGMOD.

© Copyright Partner400, 2004-2016.

Page 23

Page 24: Subroutines to Subprocedures & Service Programs, … source members can contain multiple subprocedures § Subprocedures can use other Subprocedures - But they cannot be nested When

Subprocedures are the best way to write modular RPG logic § Internal subprocedures make for great subroutine replacements

- Logic and data flow is more obvious - Local data makes maintenance faster, easier and safer

§ External subprocedures make for efficient code re-use - Especially when placed in Service Programs - Easily shared among multiple programs at "subroutine-like" speed

Get into the habit of utilizing subprocedures § Build up your own library of callable "non-built-in functions"

Resources to learn more: § 3 Good Reasons to Stop Writing Subroutines

- Susan Gantner ✴ www.ibmsystemsmag.com/ibmi/enewsletterexclusive/25782p1.aspx

§ Meat of the Matter: In RPG, Subprocedures are Useful - Scott Klement

✴ www.iprodeveloper.com/article/rpg-programming/meat-matter-rpg-subprocedures-699251

Subprocedures and Modular Code

Coming Up in Part 2 ...

1) Creating ILE modules & Service Programs

2) Externalizing our subprocedures using a Service Program

© Copyright Partner400, 2004-2016.

Page 24