Midware Ltd.

External Procedures


Sign-up for e-mail notifications

Take our weekly poll

Dow Jones Intraday

Nasdaq Intraday



The procedures we've done up to this point have been internal.  Although they are interesting, they really don't provide much more benefit than subroutines.  If we want to use a procedure in more than one program we still need to replicate it, which create the same maintenance issues as we had before.

We could put the procedure in a copy member and include it in the programs where we need it with the /COPY statement.  Because of localized variables, procedure copybooks have a distinct advantage over copybook subroutines.  We don't need to be nearly as careful with our field naming conventions, and typecasting will allow us to be much more lenient in how we pass parameters.

If we look at our summary table of the advantages of the various code re-use techniques, it now looks like this:

Maintainability Performance Data sharing Ease of use
Called Programs  
Internal Procedures
Procedure Copybooks

We can see that internal procedures are slightly more flexible when compared to subroutines.  This is primarily because of the automatic typecasting of passed parameters.

Also, we can see that we procedure copybooks compare favorably to subroutine copybooks.  This is also because of typecasting, but also because of localized variables and importing/exporting fields.

None of these methods compare favorably however, with the maintainability of externally called program.  If an internal procedure needs to be changed, all source member that contain that procedure must be changed.  If a procedure copybook is changed, all programs that use it must be re-compiled in order to see the new changes.

Externalizing procedures is the first step to creating an environment that offers the best of both worlds.

Once you've create internal procedure, externalizing them is not difficult at all.  As an example we're going to externalize our IsWorkday procedure with the following steps:

  1. Create a new source member.  In this example we'll call it PROCS1 because it will eventually contain our first collection of procedures.
  2. Copy or code the H-spec.  Even if you don't require any H-specs, add one.  Add the NOMAIN keyword to the H-specs.  NOMAIN tells the compiler that this source member will contain only procedures - no mainline code.  When the source member is eventually compiled (into a module), certain cycle code will not be included, thus reducing the size of the compiled object.
  3. If any of the procedures require access to files, code the F-specs.
  4. If global variables are needed, code the D-specs.
  5. Also in the D-specs, include the Procedure Prototypes for the included procedures, as well as any other procedures you will use.
  6. Code your procedure, beginning and ending with a P-spec.  For procedures that will be used outside of the source member, code the EXPORT keyword on the begin P-spec.  If the procedure is only to be used by other procedures within the source member, the EXPORT keyword may be eliminated.
  7. If additional procedures are required, code them as well.

Using a copybook member for Prototypes

Notice step 5 above requires you to code the Procedure Prototype in the externalized procedure source member.  The prototype is also required in all programs that will use the procedure.

In keeping with the spirit of minimizing the work required to code and use procedures, and keeping them as turnkey as possible, it is a good idea to code procedure prototypes in a separate source member and including them with the /copy statement in programs that need to use the procedure.

You may even want to consider maintaining a single source member that contains prototypes for all the procedures in your application system.  Declaring a prototype in a program does necessarily include the procedure(s) in your end program.  It only enables your program to use those procedures.  Even if you have a hundred or more procedure prototypes defined in a copy member, the overhead will be minimal.  The compile listing will however be significantly larger.

Lowest Common Denominator

Recall from our conceptual discussion of procedures that ideally we want to reduce all procedures to their lowest usable function.  These functions should then be re-used when needed, rather than re-written.

If we breakdown our IsWorkday procedure, we realize that it logically contains two different functions - one to determine the day of week, and one to determine if a date is a workday.

Having a function to determine a date's day-of-week, in addition to if it is a workday, would probably be helpful.  Therefore, now that we're beginning to externalize procedures and create a library of utilities, it makes sense to extract the day-of-week function from the IsWorkday procedure.

Our complete source member with both procedures now looks like this:

H nomain datedit(*ymd) option(*srcstmt)
 * DayOfWeek:  Calculate day of week for a given date.
 *             Returned as 0 (Sun) through 6 (Sat).
 *             Invalid date is returned as -1.
P DayOfWeek       B                    export

D DayOfWeek       PI             1  0
D  @Date                         8  0

 * Define @BaseDate as a Sunday
D @BaseDate       S               D   inz(d'2000-01-02')

D @TestDate       S               D
D @Days           S              9  0
D @DayOfWeek      S              1  0

 * If the user passed a date less than 100/00/00 we are
 * going to assume a six-digit date was passed, rather
 * an eight-digit date with a century code of "00"...
C                   if        @Date < 1000000
C     *ymd          test(d e)               @Date
C                   else
C     *iso          test(d e)               @Date
C                   endif

C                   if        %error
C                   eval      @DayOfWeek = -1
C                   else
C                   if        @Date < 1000000
C     *ymd          move                    @TestDate
C                   else
C     *iso          move                    @TestDate
C                   endif
 * How many days are there between our base date and
 * the passed date?
C     @BaseDate     subdur    @Date         @Days:*d
 * Based on the number of days, figure out the day of
 * week of the passed date (0=Sunday, 1=Monday, etc.)
C                   eval      @DayOfWeek = %rem(@Days: 7)
C                   endif

C                   return    @DayOfWeek

P DayOfWeek       E
 * IsWorkday:  Determine if a given date is a normal 
 *             workday.
P IsWorkday       B                    export

D IsWorkday       PI              N
D  @Date                         8  0

D @WorkDay        S               N
 * If date falls on a weekend, it is not a workday...
C                   if        DayOfWeek(@Date) = 0 or
C                             DayOfWeek(@Date) = 6
C                   eval      @WorkDay = *off
 * Otherwise, it is...
C                   else
C                   eval      @WorkDay = *on
C                   endif

C                   return    @WorkDay

P IsWorkday       E

Notice that we used the EXPORT keyword on both procedures.  Also notice how compact the IsWorkday procedure is now.  We don't need to do any error checking in IsWorkday anymore as it is done now in DayOfWeek.

There are many additional procedures that we may want to add, based on the DayOfWeek procedure.  For example, DOWText (day of week text) could easily be created to return the day of week in it's text format (Sunday, Monday, etc).  You may also want to create a procedure DOWAbbr that returns the day of week abbreviated text (Sun, Mon, etc) as well as procedures to test a date for each day of the week (IsSunday, IsMonday, etc).  These utilities are now very simple to write now that we have the DayOfWeek procedure.

  Back to Typecasting

Next to Creating Modules

Home Feedback Contents Search

Send mail to midware@midwareservices.com with questions or comments about this web site.
Copyright 2000 Midware, Ltd.

Last Modified:  September 06, 2000