• 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
    Rocket Software

    Two Steps Forward, No Steps Back

    For over 35 years, Rocket Software’s solutions have empowered businesses to modernize their infrastructure, unlock data value, and drive transformation – all while ensuring modernization without disruption.

    LEARN MORE

    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

  • Liam Allan Shares What’s Coming Next With Code For IBM i
  • From Stable To Scalable: Visual LANSA 16 Powers IBM i Growth – Launching July 8
  • VS Code Will Be The Heart Of The Modern IBM i Platform
  • The AS/400: A 37-Year-Old Dog That Loves To Learn New Tricks
  • IBM i PTF Guide, Volume 27, Number 25
  • Meet The Next Gen Of IBMers Helping To Build IBM i
  • Looks Like IBM Is Building A Linux-Like PASE For IBM i After All
  • Will Independent IBM i Clouds Survive PowerVS?
  • Now, IBM Is Jacking Up Hardware Maintenance Prices
  • IBM i PTF Guide, Volume 27, Number 24

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