DESCRIBE

In this section:

How to:

The DESCRIBE command enables you to define and work with objects (called classes) that cannot be defined using the standard set of classifications. Standard classifications include data types such as Alphanumeric, Numeric, and Integer (a subset of Numeric).

A class can represent a data type synonym. You can assign a name to a specific data type and then use this name as the format specification for variables. In this way, you can change the formats of multiple variables by changing the class definition.

A class can also represent an object consisting of other objects, called components of the class. You can define two types of components for a class, fields and functions (cases).

The DESCRIBE command defines the structure and behavior of a class. You then use the COMPUTE or DECLARE command to create an instance of the class. The COMPUTE command defines a global instance of the class. To create an instance that is local to a specific case, use the DECLARE command within that case.

To reference a component of a class instance, qualify the name of the component with the class instance name, separating them with a period (.). For example, consider a class called Room, which has components length and width. You can create a class instance named MyRoom in a COMPUTE or DECLARE command. For example:

COMPUTE MyRoom/Room; 

To reference the length component of the MyRoom instance, qualify the component name with the class instance name:

MyRoom.length

This is similar to qualifying a field name in a data source with its file or segment name.

Within the DESCRIBE and ENDDESCRIBE commands that define a class, you do not qualify component names for that class.

Syntax: How to Use the DESCRIBE Command

You must issue the DESCRIBE command outside of a function (case), for example, at the beginning of the procedure prior to all functions.

The syntax of the DESCRIBE command to define a new class is

DESCRIBE classname = ([superclass +] memvar/type [, memvar/type] ...) [;]
[memfunction [memfunction] ...
ENDDESCRIBE]

where:

classname

Is the name of the class that you are defining. The name is subject to the standard naming rules of the Maintain Data language. See Specifying Names for more information.

superclass

Is the name of the superclass from which to derive this class. Used only to describe a subclass.

memvar

Names one of the member variables of the class. The name is subject to the standard naming rules of the Maintain Data language. See Specifying Names for more information.

type

Is a data type (a built-in format or a class).

memfunction

Defines one of the member functions of the class. Member functions are defined the same way as other Maintain Data functions, using the CASE command. See CASE for more information.

;

For class definitions, this terminates the definition if the definition omits member functions. If it includes member functions, the semicolon is omitted and the ENDDESCRIBE command is required.

For synonym definitions, this terminates the definition and is required.

ENDDESCRIBE

Ends the class definition if it includes member functions. If it omits member functions, the ENDDESCRIBE command must also be omitted, and the definition terminates with a semicolon (;).

The syntax of the DESCRIBE command to define a synonym for a data type is

DESCRIBE synonym = datatype ;

where:

synonym

Is a synonym for a data type (a class or format). The synonym is subject to the standard naming rules of the Maintain Data language. See Specifying Names for more information.

;

For class definitions, this terminates the definition if the definition omits member functions. If it includes member functions, the semicolon is omitted and the ENDDESCRIBE command is required.

For synonym definitions, this terminates the definition and is required.

Example: Data Type Synonyms

Data type synonyms can make it easier for you to maintain variable declarations. For example, if your procedure creates many variables for names of people, and defines them all as A30, you would define a data type synonym for A30:

DESCRIBE NameType = A30;

You would then define all of the name variables as NameType:

DECLARE UserName/NameType;
COMPUTE ManagerName/NameType;
DECLARE CustomerName/NameType;

To change all name variables to A40, you could change all of them at once simply by changing one data type synonym:

DESCRIBE NameType = A40;

Example: Defining a Class and Creating an Instance

The following DESCRIBE command defines a class named Room in an architecture application. The components of the class are three fields, Width, Height, and Length:

DESCRIBE Room = (Width/I4, Height/I4, Length/I4);

The following COMPUTE command creates an instance of the Room class named abc and assigns values to the components, qualifying each component with the class name:

COMPUTE abc/Room;
abc.Width = 10;
abc.Height = 20;
abc.Length = 30;

Once the instance is created, you can use it in other Maintain Data commands. For example, the following TYPE command types each component value:

TYPE "Width=<abc.Width Height=<abc.Height Length=<abc.Length"

Class Member Functions

Reference:

Functions included within a class definition specify operations that can be performed using the components of the class.

Two function names, StartUp and CleanUp, are reserved and have specific uses.

If you define a case called StartUp, that case is executed whenever an instance of the class is created. A global instance is created at the beginning of the Maintain Data procedure. A local instance is created each time the case in which it is declared is performed.

If you define a case called CleanUp, that case is executed whenever an instance of the class reaches the end of its scope. The scope of a global instance ends after execution of the Maintain Data procedure. The scope of a local instance ends each time execution returns to the procedure that performed the case in which it was declared.

Reference: Startup Case Considerations

You can create a global instance of a class using the COMPUTE command anywhere in the Maintain Data procedure. To create an instance local to a specific case, use the DECLARE command within that case.

You can use the Startup case to assign initial values to the components of a global instance of a class.

To pass initial values for class components to the Startup case:

  • Define the Startup case to take arguments representing those components (with argument names different from the component names).
  • In the Startup case, assign the incoming parameter values to the component field names.
  • Then, in the COMPUTE command that creates the instance, specify argument values to pass to case Startup. For example, if the class is named Room, the instance is named MyRoom, and you want to assign component values length=15 and width= 10, use the following syntax to pass the values to the Startup case:
    COMPUTE MyRoom/Room(15,10); 
Note: The DECLARE command does not support passing arguments to the Startup case. However, you can always use a COMPUTE or DECLARE command to assign initial or non-initial values:
[COMPUTE|DECLARE] MyRoom/Room;
MyRoom.length = 20;
MyRoom.width = 15;

Reference: Executing Member Functions

Just as you can reference components of the class by qualifying the component names with the class instance name, you can execute a function within the class by qualifying the function name with the class instance name. For example, if the Room class contains a function called FINDAREA that takes the arguments length and width and returns the area, you can execute this function and type the returned value with the following commands:

AREA/I4 = MyRoom.FINDAREA(MyRoom.length, MyRoom.width)
TYPE "AREA = <AREA";

If the function operates on the components of the class, those components are available to the function without passing them as arguments. Therefore, if length and width are components of the Room class, the FINDAREA function does not need to take any arguments. In this case, you invoke the function as follows:

AREA/I4 = MyRoom.FINDAREA()
TYPE "AREA = <AREA";

Note that parentheses are required when invoking a member function even if the function does not take arguments.

Example: Defining a Class

The DESCRIBE command in the following Maintain Data procedure defines a class named Floor in an architecture application. The components of the class are three fields (Length, Width, and Area) and one case (PrintFloor). The COMPUTE command creates an instance of the class named MYFLOOR, assigns values to the components, and calls the PrintFloor function. Although the PrintFloor function does not take arguments, the parentheses are needed to identify PrintFloor as a function:

MAINTAIN
DESCRIBE Floor = (Length/I4, Width/I4, Area/I4)
   CASE PrintFloor
     TYPE "length=<Length  width=<Width  area=<Area";
   ENDCASE
ENDDESCRIBE
 
COMPUTE MYFLOOR/FLOOR;
MYFLOOR.Length = 15;
MYFLOOR.Width  = 10;
MYFLOOR.Area   = MYFLOOR.Length * MYFLOOR.Width;
MYFLOOR.PrintFloor();
END

The output is:

length=15 width=10 area=150

Example: Defining a Class With a Startup Case

The DESCRIBE command in the following Maintain Data procedure defines a class named Floor in an architecture application. The components of the class are three fields (Length, Width, and Area) and one case (PrintFloor). The COMPUTE command creates an instance of the class named MYFLOOR, passes values for the components to the Startup case, and calls the PrintFloor function. The Startup case initializes the component fields with the values passed in the COMPUTE command:

MAINTAIN
DESCRIBE Floor = (Length/I4, Width/I4, Area/I4)
 CASE Startup TAKES L/I4, W/I4, A/I4
   Length =  L;
   Width  =  W;
   Area   =  A;
 ENDCASE
 CASE PrintFloor
  TYPE "In PrintFloor: length=<Length  width=<Width  area=<Area";
 ENDCASE
ENDDESCRIBE
 
COMPUTE MYFLOOR/FLOOR(15, 10, 150);
TYPE "After Startup: LENGTH=<MYFLOOR.LENGTH" |
     " WIDTH=<MYFLOOR.WIDTH  AREA=<MYFLOOR.AREA";
MYFLOOR.PrintFloor();
END

The output is:

After Startup: LENGTH=15 WIDTH=10 AREA=150
In PrintFloor: length=15 width=10 area=150

Example: Defining and Using a Local Class Instance

In the following Maintain Data procedure, the DESCRIBE command defines a class named FNAME with components LAST and FIRST. The FORMNAME case concatenates the last and first names and separates them with a comma (,) and a space.

The main procedure loops through the first five records of the EMPLOYEE data source, and passes each last name and first name to case PRTFULL.

Case PRTFULL creates a local instance of the FNAME class, invokes the FORMNAME member function, and types the full name returned from that class. Although FORMNAME does not take any arguments, the parentheses used when invoking FORMNAME identify it as a function.

TYPE commands in each case illustrate the flow of control:

MAINTAIN FILE VIDEOTRK
 DESCRIBE FNAME = (LAST/A15, FIRST/A10)
 
  CASE STARTUP;
   TYPE "IN CASE STARTUP: I = <I";
  ENDCASE
 
  CASE FORMNAME RETURNS FULLNAME/A30;
   FULLNAME/A30 = LAST || ', ' | FIRST;
   TYPE "IN CASE FORMNAME: I = <I  FULLNAME = <FULLNAME";
  ENDCASE
 
  CASE CLEANUP;
   TYPE "IN CASE CLEANUP: I = <I";
  ENDCASE
 ENDDESCRIBE
 
-* MAIN PROCEDURE
 
FOR 5 NEXT CUSTID INTO CUSTSTK;
REPEAT 5 I/I1 = 1;
COMPUTE LAST/A15 = CUSTSTK(I).LASTNAME;
COMPUTE FIRST/A10 = CUSTSTK(I).FIRSTNAME;
TYPE  "IN MAIN PROCEDURE: I = <I  LAST = <LAST  FIRST = <FIRST";
PERFORM PRTFULL(LAST, FIRST);
ENDREPEAT I = I+1;
 
 CASE PRTFULL TAKES LAST/A15, FIRST/A10;
-* MEMNAME IS A LOCAL VARIABLE
  DECLARE  MEMNAME/FNAME;
  MEMNAME.LAST=LAST;
  MEMNAME.FIRST=FIRST;
  NEWNAME/A30  = MEMNAME.FORMNAME();
  TYPE "IN CASE PRTFULL: I = <I  MEMBER NAME IS <NEWNAME";
 ENDCASE
END

The output shows that the Startup case is called prior to each invocation of case PRTFULL (which defines the local instance), and the Cleanup case is called at the end of each invocation of case PRTFULL:

IN MAIN PROCEDURE: I = 1 LAST = CRUZ FIRST = IVY
IN CASE STARTUP: I = 1
IN CASE FORMNAME: I = 1 FULLNAME = CRUZ, IVY
IN CASE PRTFULL: I = 1 MEMBER NAME IS CRUZ, IVY
IN CASE CLEANUP: I = 1
IN MAIN PROCEDURE: I = 2 LAST = HANDLER FIRST = EVAN
IN CASE STARTUP: I = 2
IN CASE FORMNAME: I = 2 FULLNAME = HANDLER, EVAN
IN CASE PRTFULL: I = 2 MEMBER NAME IS HANDLER, EVAN
IN CASE CLEANUP: I = 2
IN MAIN PROCEDURE: I = 3 LAST = WILSON FIRST = KELLY
IN CASE STARTUP: I = 3
IN CASE FORMNAME: I = 3 FULLNAME = WILSON, KELLY
IN CASE PRTFULL: I = 3 MEMBER NAME IS WILSON, KELLY
IN CASE CLEANUP: I = 3
IN MAIN PROCEDURE: I = 4 LAST = KRAMER FIRST = CHERYL
IN CASE STARTUP: I = 4
IN CASE FORMNAME: I = 4 FULLNAME = KRAMER, CHERYL
IN CASE PRTFULL: I = 4 MEMBER NAME IS KRAMER, CHERYL
IN CASE CLEANUP: I = 4
IN MAIN PROCEDURE: I = 5 LAST = GOODMAN FIRST = JOHN
IN CASE STARTUP: I = 5
IN CASE FORMNAME: I = 5 FULLNAME = GOODMAN, JOHN
IN CASE PRTFULL: I = 5 MEMBER NAME IS GOODMAN, JOHN
IN CASE CLEANUP: I = 5
 
TRANSACTIONS: COMMITS   =        1 ROLLBACKS =     0
SEGMENTS  : INCLUDED  =        0 UPDATED   =     0 DELETED   =        0

Example: Defining and Using a Global Class Instance

In the following Maintain Data procedure, the DESCRIBE command defines a class named FNAME with components LAST and FIRST. The FORMNAME case concatenates the last and first names and separates them with a comma (,) and a space.

The main procedure loops through the first five records of the EMPLOYEE data source, and passes each last name and first name to case PRTFULL.

Case PRTFULL creates a global instance of the FNAME class, invokes the FORMNAME member function, and types the full name returned from that class. Although FORMNAME does not take any arguments, the parentheses used when invoking FORMNAME identify it as a function.

TYPE commands in each case illustrate the flow of control:

MAINTAIN FILE VIDEOTRK
 DESCRIBE FNAME = (LAST/A15, FIRST/A10)
  CASE STARTUP TAKES LASTNAME/A15, FIRSTNAME/A10;
   TYPE "IN CASE STARTUP: I = <I  LAST = <LASTNAME  FIRST = <FIRSTNAME";
   LAST = LASTNAME;
   FIRST = FIRSTNAME;
  ENDCASE
 
  CASE FORMNAME RETURNS FULLNAME/A30;
    FULLNAME/A30 = LAST || ', ' | FIRST;
    TYPE "IN CASE FORMNAME: I = <I  FULLNAME = <FULLNAME";
  ENDCASE
 
  CASE CLEANUP;
    TYPE "IN CASE CLEANUP: I = <I  ";
  ENDCASE
 ENDDESCRIBE
 
-*MAIN PROCEDURE
 
FOR 5 NEXT CUSTID INTO CUSTSTK;
 
REPEAT 5 I/I1 = 1;
 COMPUTE LAST/A15 = CUSTSTK(I).LASTNAME;
 COMPUTE FIRST/A10 = CUSTSTK(I).FIRSTNAME;
 TYPE  "IN MAIN PROCEDURE: I = <I  LAST = <LAST  FIRST = <FIRST";
 PERFORM PRTFULL(LAST, FIRST);
ENDREPEAT I = I+1;
 
 CASE PRTFULL TAKES LAST/A15, FIRST/A10;
  COMPUTE MEMNAME/FNAME('ABEL', 'AARON');
  NEWNAME/A30  = MEMNAME.FORMNAME();
  TYPE "IN CASE PRTFULL: I = <I  MEMBER NAME IS <NEWNAME";
 ENDCASE
END

The output shows that the Startup case is called at the start of the Maintain Data procedure, and the Cleanup case is called following the execution of the entire Maintain Data procedure:

IN CASE STARTUP: I = 0 LAST = ABEL FIRST = AARON
 
IN MAIN PROCEDURE: I = 1 LAST = CRUZ FIRST = IVY
IN CASE FORMNAME: I = 1 FULLNAME = CRUZ, IVY
IN CASE PRTFULL: I = 1 MEMBER NAME IS CRUZ, IVY
IN MAIN PROCEDURE: I = 2 LAST = HANDLER FIRST = EVAN
IN CASE FORMNAME: I = 2 FULLNAME = HANDLER, EVAN
IN CASE PRTFULL: I = 2 MEMBER NAME IS HANDLER, EVAN
IN MAIN PROCEDURE: I = 3 LAST = WILSON FIRST = KELLY
IN CASE FORMNAME: I = 3 FULLNAME = WILSON, KELLY
IN CASE PRTFULL: I = 3 MEMBER NAME IS WILSON, KELLY
IN MAIN PROCEDURE: I = 4 LAST = KRAMER FIRST = CHERYL
IN CASE FORMNAME: I = 4 FULLNAME = KRAMER, CHERYL
IN CASE PRTFULL: I = 4 MEMBER NAME IS KRAMER, CHERYL
IN MAIN PROCEDURE: I = 5 LAST = GOODMAN FIRST = JOHN
IN CASE FORMNAME: I = 5 FULLNAME = GOODMAN, JOHN
IN CASE PRTFULL: I = 5 MEMBER NAME IS GOODMAN, JOHN
 
TRANSACTIONS: COMMITS   =        1 ROLLBACKS =     0
SEGMENTS  : INCLUDED  =        0 UPDATED   =     0 DELETED   =        0
 
IN CASE CLEANUP: I = 6

Defining and Using Superclasses and Subclasses

Reference:

After you describe a class, you can derive other classes from it. Subclasses inherit member variables and functions from their superclasses.

Order of classes matters when defining superclasses and subclasses. You must describe a superclass prior to its subclasses.

A class can also use another class as one of its components. Again, order matters. You must describe the class you are using as a component prior to the class that uses it.

Example: Defining a Subclass

The following example describes two classes, Floor and Room. Floor consists of components Length and Width and member function FloorArea.

Room is a subclass of Floor. It inherits components Length and Width and member function FloorArea. It adds component Depth and function RoomVolume.

The main procedure creates an instance of Room called MYROOM. It then assigns values to the components, including inherited components Length and Width. It invokes the inherited member function FloorArea as well as the RoomVolume function.

The main procedure then types the component values and the values returned by the member functions.

MAINTAIN
 
  DESCRIBE Floor = (Length/I4, Width/I4)
   CASE FloorArea  RETURNS Area/I4;
    Area = Length * Width;
   ENDCASE
  ENDDESCRIBE
 
  DESCRIBE Room  = (Floor +  Depth/I4)
   CASE RoomVolume RETURNS Volume/I4;
    Volume = FLOORAREA() * Depth;
   ENDCASE
  ENDDESCRIBE
 
COMPUTE MYROOM/ROOM;
MYROOM.LENGTH = 15;
MYROOM.WIDTH = 10;
MYROOM.DEPTH = 10;
AREA/I4 = MYROOM.FLOORAREA();
VOLUME/I4 = MYROOM.RoomVolume();
TYPE "LENGTH=<MYROOM.Length, WIDTH=<MYROOM.Width, " |
     "DEPTH=<MYROOM.DEPTH, AREA=<AREA, "          |
     "VOLUME=<VOLUME";
END

The output is:

LENGTH=15, WIDTH=10, DEPTH=10, AREA=150, VOLUME=1500

Example: Using a Class as a Component of Another Class

The following example describes three classes: RoomDetail, Floor, and Room.

RoomDetail has no member functions. It consists of two components, Depth and RmType.

Floor consists of components Length and Width and member function FloorArea.

Room is a subclass of Floor. It inherits components Length and Width and member function FloorArea. In addition, it has member function RoomVolume and component RmType, which is an instance of class RoomDetail. When referring to the components of RoomDetail, you must qualify them with their instance name. For example, the Rtype component is referenced as follows:

RmType.Rtype

The main procedure creates an instance of Room called MYROOM. It then assigns values to the components, including inherited components Length and Width. When assigning values to the components of the RmType instance of the RoomDetail class, it must qualify them with the instance name, MYROOM. Since these names already had one level of qualification when they were referenced in the Room class, they now have two levels of qualification. For example, the following assigns the value 10 to the Depth component:

MYROOM.RmType.Depth = 10;

Note that when no ambiguity in the variable name will result, only one level of qualification is actually enforced. MYROOM.Depth is understood as MYROOM.RmType.Depth since Depth does not appear in any other context.

The main procedure invokes the inherited member function FloorArea, as well as the RoomVolume function, then types the component values and the values returned by the member functions.

MAINTAIN
  DESCRIBE RoomDetail = (Depth/I4, Rtype/A10);
 
  DESCRIBE Floor = (Length/I4, Width/I4)
   CASE FloorArea  RETURNS Area/I4;
    Area = Length * Width;
   ENDCASE
  ENDDESCRIBE
 
  DESCRIBE Room  = (Floor + RmType/RoomDetail)
   CASE RoomVolume RETURNS Volume/I4;
    Volume = FLOORAREA() * RmType.Depth;
    TYPE "ROOM TYPE IS <RmType.Rtype";
   ENDCASE
  ENDDESCRIBE
 
COMPUTE MYROOM/ROOM;
MYROOM.LENGTH = 15;
MYROOM.WIDTH = 10;
MYROOM.RmType.Depth = 10;
MYROOM.RmType.Rtype = 'DINING    ';
AREA/I4 = MYROOM.FLOORAREA();
VOLUME/I4 = MYROOM.RoomVolume();
TYPE "LENGTH=<MYROOM.LENGTH, WIDTH=<MYROOM.WIDTH, "|
"DEPTH=<MYROOM.rmtype.DEPTH, AREA=<AREA," |
  " VOLUME=<VOLUME";
END

The output is:

ROOM TYPE IS DINING
LENGTH=15, WIDTH=10, DEPTH=10, AREA=150, VOLUME=1500

Reference: Commands Related to DESCRIBE

  • DECLARE. Creates local and global variables, including objects.
  • COMPUTE. Creates global variables, including global objects, and assigns values to existing variables.