• The Four Hundred
  • Subscribe
  • Media Kit
  • Contributors
  • About Us
  • Contact
Menu
  • The Four Hundred
  • Subscribe
  • Media Kit
  • Contributors
  • About Us
  • Contact
  • Guru: Global Variables in Modules

    December 13, 2021 Ted Holt

    When I first learned to program computers (RPG II, COBOL 74), the only kind of variables I knew of were global variables. Any statement within a program was able to use any variable.  It was not until I started my computer science degree that I found out about local variables, which are known to only part of a program. Since that time, it has been my practice to use local variables as much as possible and global variables only when necessary.

    Ideally an RPG program, service program, module, or subprocedure would have no global variables at all, but I don’t live in an ideal world. Today I want to write about an appropriate use of global variables in a module. When you consider that the typical RPG program or service program in most shops is built from only one module, what I have to say applies to RPG programming in general, not just *MODULE objects.

    For an illustration, I’ll use the topic of inventory. Let’s say that we work for an organization that stores items in warehouses. Each item has attributes, of course, among them weight and dimensions (length, width, height). Here’s a simplified item table with the columns we need for this illustration.

    create table items
      ( ID                     char    ( 6)   primary key,
        Description for Descr  varchar (25),
        Weight                 dec   ( 9, 3),
        Height                 dec   ( 7, 3),
        Width                  dec   ( 7, 3),
        Length                 dec   ( 7, 3));
    
    insert into items values
    ('AB-101', '#10 Widget', 2.1, 7, 6, 10.2);
    

    Since I am American, I’ll say that we store weights and measures in U.S. customary units, or as I usually call them, English units. However, just because the data is stored in English units does not mean that the user should have to deal with English measurements. One of the best features of a database management system (as opposed to data files, like the System/36 files we had to use in antiquity) is that we can perceive the data in ways that differ from how the data is stored. (This is the concept behind views.) Therefore, even though the weight and dimensions of an item are stored in English measurements, the user should be able to view them in metric units instead.

    Let’s use the same concept in our programming. Suppose we have a service program of subprocedures that retrieve and manipulate item data, similar to the inventory service program I described recently. Among the item-related subprocedures are some that return the weight and measurements of an item. For example, there’s a WeightOf subprocedure that operates like a built-in function. Give it an item number and it returns the weight of the item.  There are also LengthOf, WidthOf, and HeightOf subprocedures that return the various dimensions of the item. There is a subprocedure that returns all three dimensions in parameters.

    Since I want these subprocedures to be able to return the data in either English or metric units, I would need to pass a parameter to each subprocedure to specify which system of measurements to use.  There would be nothing wrong with that approach.

    ItemWeight = WeightOf (SomeItem: ‘KG’);
    

    However, an alternative is to tell the service program once which system to use and be done with it.  For that, we can define a global variable in the module. All the weight-and-measure-related subprocedures can check the value of the global variable and behave accordingly.

    Here’s part of copybook INVITEMS.

    **free
    dcl-s  MeasurementSystem_t    char(1)      template;
    dcl-c  AmericanSystem         const('0');
    dcl-c  MetricSystem           const('1');
    
    dcl-s  Weight_t          packed(9:5)  template;
    dcl-s  Dimension_t       packed(7:3)  template;
    dcl-s  ItemNumber_t         char(6)   template;
    
    dcl-pr    WeightOf  like(Weight_t);
       inItemNumber     like(ItemNumber_t)   const;
    end-pr;
    
    dcl-pr    HeightOf  like(Dimension_t);
       inItemNumber     like(ItemNumber_t)   const;
    end-pr;
    
    dcl-pr    SetMeasure;
       inMeasurementSystem     like(MeasurementSystem_t)   const;
    end-pr;
    
    dcl-pr    GetMeasure     like(MeasurementSystem_t);
    end-pr;
    

    First is an enumerated data type that lists the supported measurement systems. It’s in this copybook because the callers need to reference the constants, and may need to reference the template.

    Next are some templates for common data. As I wrote recently, these definitions are how the caller perceives the data, which is not necessarily how the data are stored in the database. In this case, the data definitions match the definitions in the database.

    Last are enough procedure prototypes to illustrate the concepts. Notice WeightOf and HeightOf.  They return data, but there is no parameter to specify which system of weights and measures to use. Instead, the caller calls the SetMeasure subprocedure to let the subprocedures know which system to use.

    Now that we understand the interfaces, let’s see the implementation. This is module INVITEMS.

    **free
    ctl-opt   nomain  option(*srcstmt: *nodebugio);
    
    /include prototypes,invitems
    /include prototypes,assert
    
    dcl-s  MeasurementSystem   like(MeasurementSystem_t) inz(AmericanSystem);
    
    dcl-c  LBS_TO_KG_FACTOR       const(0.4535924);
    dcl-c  INCHES_TO_CM_FACTOR    const(2.54);
    
    dcl-c  C_SQLEOF               const('02000');
    
    dcl-proc  WeightOf   export;
    
       dcl-pi *n         like(Weight_t);
          inItemNumber   like(ItemNumber_t)   const;
       end-pi;
    
       dcl-s  ItemWeight     like(Weight_t);
    
       exec sql
          select it.Weight
            into :ItemWeight
            from items as it
           where it.ID = :inItemNumber;
    
       select;
          when SqlState = C_SQLEOF;
             clear ItemWeight;
          when SqlState > C_SQLEOF;
             assert (*off: 'Error in Weight function.');
       endsl;
    
       if MeasurementSystem = MetricSystem;
          eval(h) ItemWeight *= LBS_TO_KG_FACTOR;
       endif;
    
       return ItemWeight;
    
    end-proc  WeightOf;
    
    dcl-proc  HeightOf   export;
    
       dcl-pi *n         like(Dimension_t);
          inItemNumber   like(ItemNumber_t)   const;
       end-pi;
    
       dcl-s  ItemHeight     like(Dimension_t);
    
       exec sql
          select it.Height
            into :ItemHeight
            from items as it
           where it.ID = :inItemNumber;
    
       select;
          when SqlState = C_SQLEOF;
             clear ItemHeight;
          when SqlState > C_SQLEOF;
             assert (*off: 'Error in Height function.');
       endsl;
    
       if MeasurementSystem = MetricSystem;
          eval(h) ItemHeight *= INCHES_TO_CM_FACTOR;
       endif;
    
       return ItemHeight;
    
    end-proc  HeightOf;
    
    dcl-proc  SetMeasure  export;
    
       dcl-pi *n;
          inMeasurementSystem like(MeasurementSystem_t) const;
       end-pi;
    
       MeasurementSystem = inMeasurementSystem;
    
    end-proc  SetMeasure;
    
    dcl-proc  GetMeasure   export;
    
       dcl-pi *n         like(MeasurementSystem_t);
       end-pi;
    
       return MeasurementSystem;
    
    end-proc  GetMeasure;
    

    The variable MeasurementSystem is declared before (i.e., outside of) the subprocedures. This means that the subprocedures can reference it.

    There are two ways for a caller to change the value of the MeasurementSystem global variable.

    The first way, which I don’t like, is to export the variable in the module and import it in the caller.

    In the module:

    dcl-s  MeasurementSystem   like(MeasurementSystem_t) export;
    

    In the callers:

    dcl-s  MeasurementSystem   like(MeasurementSystem_t) import;
    

    With this method, the caller changes the MeasurementSystem variable as it would any other variable.

    MeasurementSystem = MetricSystem;
    

    Now that you’ve seen it, I recommend you forget it.

    The second way, which I do like, is to use a “setter” routine. In this module, the setter is subprocedure SetMeasure.  A caller passes a parameter to SetMeasure, which changes the value of the global variable.  If a caller needs to know the value of a global variable, it uses a “getter”.  I didn’t make this up.  Getters and setters are common in object-oriented languages like Java and C++.

    The assert subprocedure came from here, which is where I normally get it when I need to install it on another system.

    Here’s a short calling program that uses these routines.

    **free                                                
    ctl-opt  actgrp(*new) option(*srcstmt: *nodebugio)    
             bnddir('SYSTEM');                            
                                                          
    dcl-f qsysprt printer(132);                           
                                                          
    /include prototypes,InvItems                          
                                                          
    dcl-s  UOM         like(MeasurementSystem_t);         
    dcl-s  ItemWeight  like(Weight_t);                    
    dcl-s  ItemHeight  like(Dimension_t);                 
                                                          
    *inlr = *on;                                          
    UOM = GetMeasure ();                                  
    writeln ('1. UOM=/' + UOM + '/');                     
                                                          
    SetMeasure (AmericanSystem);                          
    ItemWeight = WeightOf ('AB-101');                     
    writeln ('2. Weight=/' + %char(ItemWeight) + '/');    
    ItemHeight = HeightOf ('AB-101');                     
    writeln ('3. Height=/' + %char(ItemHeight) + '/');    
                                                          
    SetMeasure (MetricSystem);                            
    ItemWeight = WeightOf ('AB-101');                           
    writeln ('4. Weight=/' + %char(ItemWeight) + '/');          
    ItemHeight = HeightOf ('AB-101');                           
    writeln ('5. Height=/' + %char(ItemHeight) + '/');          
                                                                
    UOM = GetMeasure ();                                        
    writeln ('6. UOM=/' + UOM + '/');                           
                                                                
    return;                                                     
                                                                
    dcl-proc writeln;                                           
       dcl-pi *n;                                               
          inString   varchar(132)   const;                      
       end-pi;                                                  
                                                                
       dcl-ds   ReportLine   len(132)   end-ds;                 
                                                                
       ReportLine = inString;                                   
       write qsysprt ReportLine;                                
                                                                
    end-proc writeln;                                           
    

    And here’s the output.

    1. UOM=/0/
    2. Weight=/2.10000/
    3. Height=/7.000/
    4. Weight=/.95254/
    5. Height=/17.780/
    6. UOM=/1/
    

    I’ll leave it to you to validate that the code worked correctly.

    I can’t say enough bad about global variables. They have been the source of innumerable bugs and wasted so much of my time. To say that I hate them is to put it mildly. But I freely admit that they have their uses.

    Editor’s Note: Don’t miss Ted’s special note in this issue of The Four Hundred.

    RELATED STORIES

    Guru: Enumerated Data Types In RPG

    Guru: Abstract Data Types and RPG

    The RUN Utility: Call a Program with Correctly Formatted Parameters

    Guru: Success Requires Many Teachers

    Share this:

    • Reddit
    • Facebook
    • LinkedIn
    • Twitter
    • Email

    Tags: Tags: 400guru, C, FHG, Four Hundred Guru, IBM i, Java, RPG, System/36

    Sponsored by
    WorksRight Software

    Do you need area code information?
    Do you need ZIP Code information?
    Do you need ZIP+4 information?
    Do you need city name information?
    Do you need county information?
    Do you need a nearest dealer locator system?

    We can HELP! We have affordable AS/400 software and data to do all of the above. Whether you need a simple city name retrieval system or a sophisticated CASS postal coding system, we have it for you!

    The ZIP/CITY system is based on 5-digit ZIP Codes. You can retrieve city names, state names, county names, area codes, time zones, latitude, longitude, and more just by knowing the ZIP Code. We supply information on all the latest area code changes. A nearest dealer locator function is also included. ZIP/CITY includes software, data, monthly updates, and unlimited support. The cost is $495 per year.

    PER/ZIP4 is a sophisticated CASS certified postal coding system for assigning ZIP Codes, ZIP+4, carrier route, and delivery point codes. PER/ZIP4 also provides county names and FIPS codes. PER/ZIP4 can be used interactively, in batch, and with callable programs. PER/ZIP4 includes software, data, monthly updates, and unlimited support. The cost is $3,900 for the first year, and $1,950 for renewal.

    Just call us and we’ll arrange for 30 days FREE use of either ZIP/CITY or PER/ZIP4.

    WorksRight Software, Inc.
    Phone: 601-856-8337
    Fax: 601-856-9432
    Email: software@worksright.com
    Website: www.worksright.com

    Share this:

    • Reddit
    • Facebook
    • LinkedIn
    • Twitter
    • Email

    As I See It: The Ideal Workplace 2021: An IBM i Year in Review, Part One

    2 thoughts on “Guru: Global Variables in Modules”

    • ilookatcodeallday says:
      December 20, 2021 at 8:59 am

      Are module-global variables safe to use with a named activation group?

      Reply
    • Ted Holt says:
      December 20, 2021 at 4:50 pm

      Yes. Activation group is irrelevant.

      Reply

    Leave a Reply Cancel reply

TFH Volume: 31 Issue: 82

This Issue Sponsored By

  • UCG Technologies
  • WorksRight Software
  • Eradani
  • Raz-Lee Security
  • Racksquared

Table of Contents

  • In The IBM i Trenches With: IBM Champion Ash Giddings
  • 2021: An IBM i Year in Review, Part One
  • Guru: Global Variables in Modules
  • As I See It: The Ideal Workplace
  • The Four Hundred Guru Retires

Content archive

  • The Four Hundred
  • Four Hundred Stuff
  • Four Hundred Guru

Recent Posts

  • POWERUp 2025 –Your Source For IBM i 7.6 Information
  • Maxava Consulting Services Does More Than HA/DR Project Management – A Lot More
  • Guru: Creating An SQL Stored Procedure That Returns A Result Set
  • As I See It: At Any Cost
  • IBM i PTF Guide, Volume 27, Number 19
  • IBM Unveils Manzan, A New Open Source Event Monitor For IBM i
  • Say Goodbye To Downtime: Update Your Database Without Taking Your Business Offline
  • i-Rays Brings Observability To IBM i Performance Problems
  • Another Non-TR “Technology Refresh” Happens With IBM i TR6
  • IBM i PTF Guide, Volume 27, Number 18

Subscribe

To get news from IT Jungle sent to your inbox every week, subscribe to our newsletter.

Pages

  • About Us
  • Contact
  • Contributors
  • Four Hundred Monitor
  • IBM i PTF Guide
  • Media Kit
  • Subscribe

Search

Copyright © 2025 IT Jungle