On the Road with Object REXX
by Mike Baryla
Hesitant about exploring the mysterious world of object-oriented programming? Unsure about what provisions to pack for the journey? A good beginning is Object REXX.
Object REXX is an extended, object-oriented version of traditional REXX, a long-time favorite of no-nonsense programmers. These object-oriented concepts, coupled with the flexibility and immediacy of REXX, can ease your journey into the object world.
The bus is boarding, so hop on. In our short trip through Object Land, we'll navigate class hierarchies, thread around objects, and climb to the summit of SOM. Our adventure begins on - you guessed it - the savannah of syntax.
Traveling with Old Friends
You don't have to say good-bye to traditional REXX functions when you embark on your journey through Object Land. Adding object-oriented extensions to the REXX language does not change its existing functions and instructions. Consequently, all of your current REXX programs will work without change. (Of course, the programs must be free of errors.) You can continue to use REXX's procedural instructions and intermix the use of objects as you become more comfortable with the technology.
In object-oriented technology, programs use objects to model the real world. Similar objects are grouped into classes, and the classes themselves are arranged in hierarchies. A billing application, for example, can have an invoice class and a receipt class. These two classes might be members of a forms class. Individual invoices are objects that are instances of the invoice class.
Each object contains all the data associated with it. To get at the data, you write instructions that send messages to the objects. These messages activate segments of code known as methods. For an invoice object, you might want a CREATE method, DISPLAY method, PRINT method, UPDATE method, and ERASE method.
Figure 1. The anatomy of an object; object variables are accessible only through methods.
As an object-oriented programmer, you solve problems by identifying and classifying objects related to the problem. Then, you determine what methods you need for those objects. Finally, you write the instructions to generate the classes, create the objects, and implement the methods. Your main program consists of instructions that send messages to objects.
You might be wondering how it is possible to add objects, classes, and methods to REXX without breaking every REXX program ever written. The trick is in how REXX handles the variables. Traditional REXX stores all data as strings. Strings represent character, as well as numeric, data. From an object-oriented perspective, you might say that traditional REXX has one class of objects: strings. To use object-oriented terminology, each string variable is an object that is an instance of the string class.
In Object REXX, variables can reference objects other than strings. In addition to a string class, Object REXX includes an array class, a queue class, a stream class, and many other useful classes for building programs. (Yes, it's still your job to create classes that are unique to your application, such as invoices.) These new classes help you organize your data better. You manipulate the objects from these new REXX classes by methods, instead of traditional functions. To activate a method, you send the object a message. For example, instead of using the SUBSTR function on a string variable name, you send a SUBSTR message to the string object. Here is the old way:
And here is the new way:
In Object REXX, both ways are valid. In fact, if you use the old way, Object REXX converts it internally to the message format for processing. The tilde (~) character is the REXX message send operator (also known as the twiddle). The object receiving the message is to the left of the twiddle. The message is to the right. In this example, the SUBSTR message is sent to the Name object. The numbers in parentheses (2,3) are arguments that are sent as part of the message. The SUBSTR method is run on the object in Name, and the resulting string object is assigned to n.
Object REXX provides methods for the new classes, but not the equivalent built-in functions. For example, suppose you want to use the new REXX array object instead of the traditional string-based stem variables. To create an array object of five elements, you send a NEW message to the array class as follows:
A new instance of the array class is created and assigned to the variable myarray. (A period precedes the names of built-in classes in expressions, so .ARRAY is correct, not just ARRAY.) The myarray array object has five elements. Some of the methods for array objects are PUT, AT, REMOVE, and SIZE.
Mingling with the Natives
To learn how to use the new objects in REXX clauses, let's look at a few examples using the myarray array object. After myarray is created, you'll want to assign values to it. One way is with the PUT method. Here, we associate the string object Hello with the third element of myarray:
One way to retrieve values from an array object is by sending it an AT message. In the next example, the SAY instruction displays the third element of myarray:
And the result is:
The SAY instruction expects the string object as input, and that is exactly what AT returns in this case. So, what happens if you put a non-string object in the SAY instruction? SAY sends a STRING message to the object. The STRING method for REXX's built-in objects returns a human-readable string representation for the object. In our example, the STRING method for myarray returns the string an array:
say myarray /* SAY sends STRING message to MYARRAY */
And the result is:
Whenever a method returns a string, you can use it within expressions that require a string. For example, the AT method returns a string object, so you can put an expression containing the AT method inside a string function:
And the result is:
This example gets the same result using only methods:
Object REXX evaluates the expression from left to right. You also can use parentheses to enforce an order of evaluation.
One advantage of using REXX objects instead of traditional strings is that the REXX objects offer more specialized functions. You can, for example, use the ITEMS method to determine the number of items currently in an array:
say myarray~items /* Displays 1 (other elements are null) */
To do the same thing with stem variables, you need to write instructions to loop through the entire array and count the elements that aren't null.
Finally for some methods, you don't need to use the twiddle character. The exceptions have been made to improve the usability of the language. This example uses the [ ]= method and [ ] method to set and retrieve array elements:
myarray='the fourth element' say myarray
Although the above two statements look like an ordinary array assignment and array reference, they are really messages to the myarray object. You can prove it to yourself by executing these equivalent statements, which use the twiddle to send the messages.
myarray~'='('a new test',4) say myarray~''(4)
Similarly, expression operators (such as +, -, /, and *) are actually methods. But, you normally would not use the twiddle operator to send them:
say 2+3 /* Displays 5 */ say 2~'+'(3) /* Displays 5 */
Although REXX provides many classes that serve as useful building blocks for programs, you'll soon want to create your own classes. By analyzing your problem in terms of objects, you can determine what classes need to be created and begin to take advantage of object-oriented technology.
There are two ways to create classes in Object REXX: you can use a directive (a new kind of REXX clause), or you can use a SUBCLASS method. In most cases, you'll want to use directives because they provide an easy way for you to save your class definitions and share them with others.
Object REXX directives begin with two colons (::). The ::CLASS directive defines a class. It also indicates what class the new class belongs to (that is, its superclass), whether the new class inherits methods, whether the new class can be shared with other programs, and so on. Following the ::CLASS directive, you would place ::METHOD directives. A ::METHOD directive defines a method of the class and is immediately followed by the REXX code for that method.
The following example shows you how to create the PART class and defines three methods:
/* PARTDEF.CMD -- Class and method definition file */ /* Define the PART class as a PUBLIC class */ ::class part public /* Define the INIT method to initialize object variables */ ::method unit expose name description number cost use arg name, description, number, cost /* Define the STRING method to return a string with the part name */ ::method string expose name return "Part name:" name /* Define the PRICE method to calculate price based on an input */ /* quantity */ ::method price expose cost use arg quantity if quantity>10 then markup=1.15 /* Markup if more than 10 are purchased */ else markup=1.25 /* Markup if 10 or fewer are purchased */ return format((cost*markup)*quantity,,2)
On the ::CLASS directive, the keyword PUBLIC indicates that the class is to be shared with other programs. The ::METHOD directives define the methods INIT, STRING, and PRICE. Whenever Object REXX creates a new instance of a class, it calls the INIT method for the class. (We'll see how to create an instance of a class in the next example.) Our INIT method uses an EXPOSE instruction to make the name, description, number, and cost variables available to other methods. These exposed variables are referred to as object variables. They are associated with a particular instance of a class.
INIT expects to be passed four arguments. The USE instruction assigns these four arguments to name, description, number, and cost, respectively. Because those variables are exposed, the values are available to other methods of the same object that also expose the variables.
The STRING method returns the string Part name:, followed by the name of a part. The STRING method doesn't expect any arguments. It uses the EXPOSE instruction to tap into the object variables. The RETURN instruction returns the result string.
The PRICE method calculates the retail price. It expects one argument, the quantity purchased, as input. PRICE needs the cost for calculations, so the cost object variable is exposed. The PRICE method gives a discount for quantities of 10 or more. The markup variable is not available to other methods because it is not exposed.
Now, let's see how to use the Part class:
/* BUYPART.CMD - use the Part class */ myparta=.part~new('Widget','A small widge',12345,5.95) mypartb=.part~new('Framistat','Device to control frams',899,10.00) say myparta say mypartb say myparta~price(20) say mypartb~price(20) exit
The BUYPART program creates two parts, which are instances of the PART class. It then displays the names of the two parts, followed by purchase prices for quantities of 20.
First, notice that a new directive is used: ::REQUIRES. Object REXX processes all directives before executing your program. The ::REQUIRES directive indicates that the program needs access to public class definitions that are in another program. In this case, the ::REQUIRES directive refers to the PARTDEF program, which contains the PART definition.
Next, look at the two assignment statements for myparta and mypartb. These assignment statements create two objects that are instances of the PART class. The application creates the objects by sending a NEW message to the PART class. The NEW message causes the INIT method to be invoked as part of object creation. Our INIT method takes the four arguments you provide and makes them part of the variable pool for the object. Each object has its own set of variables (name, description, number, and cost).
The SAY instruction sends a STRING message to the object. The first SAY instruction sends the STRING message to myparta. The STRING method accesses the name object variable for myparta and returns it as part of a string. The second SAY instruction sends the STRING message again, but to a different object: mypartb. Because the STRING method is invoked for mypartb, it automatically accesses the variables for mypartb. You don't need to pass the name of the object to the method to distinguish different sets of object variables. Object REXX keeps track of the variables for you.
The third SAY statement displays a price. The application sends the PRICE message to myparta for a quantity of 20. The PRICE method computes the price and returns a number (which is actually a string). The SAY instruction displays the number. The last SAY instruction also uses the PRICE method, but for mypartb.
A highlight on any tour through Object Land is IBM's System Object Model (SOM). From an Object REXX program, you can use classes, objects, and methods created with SOM. You no longer need to work in Smalltalk, C, or C++ to use SOM objects.
Because the OS/2 Workplace Shell is composed of SOM objects, you now also have complete control over the Workplace Shell from an Object REXX program.
Previously, classic REXX provided some access to Workplace Shell objects from utility functions in the REXXUTIL function package (such as SysQueryClassList, SysCreateObject, SysSetObjectData and so on). Now you have access to all workplace classes and methods. The following example shows how to create a folder on the desktop by sending a wpclsNEW message to the WPFolder class.
folder = .wpFolder~wpclsNew('New Folder', /* Folder will have title "New Folder" */ '',, /* No Specific Setup String */ .wpDeskTop,, /* Create new folder on the Desktop */ 1) /* Lock the object to start with */ folder~wpOpen(0,0,0) /* Open the folder (Default View) */ folder~wpClose /* Now close the folder. */
The Object REXX documentation on The Developer Connection for OS/2 CD-ROM does not include documentation for the workplace classes and methods. They are documented in the OS/2 Technical Library. The documentation for the methods is in the form of C-language function calls. To use the function calls from REXX, you:
- Use the function name as the REXX method name.
- Omit the first parameter from the REXX method. It is usually a pointer to a class object or a pointer to an object. The argument appears elsewhere in the REXX expression as the message receiver (to the left of the twiddle).
- Use the other parameters in order. When string input is required, simply use a REXX string. You don't need to worry about supplying a zero terminator. For numeric or Boolean values, use REXX numbers (which are actually strings).
In some cases, you'll be using several methods to do a task. Often, one of these methods will return a pointer to an object, and this pointer will be needed on other calls. To do this, assign the pointer to a regular REXX variable, and then pass this variable on subsequent methods. For functions that require pointers to classes, use the class name as shown in the preceding example (.wpFolder). Remember to precede the class name with a period.
In addition to WorkPlace Shell objects, Object REXX can also create objects and run methods for classes created using SOM.
To use a SOM class in your program, you must first import it. There are two ways to import a SOM class. You can use a ::CLASS directive, or you can send an IMPORT message to the REXX object named SOM.
To import a SOM class named Animal using a directive, specify the EXTERNAL keyword as follows:
::class beasts external 'SOM Animal'
The literal string following EXTERNAL indicates where to import the class from and the name of that class. In this case, you are importing the Animal class from SOM. The name Beasts is given to the Object REXX class. You can use the same name (Animal) if you wish.
The second way to import a class is by sending a message to a REXX object named SOM. The SOM object has an IMPORT method for importing classes:
beastclass = .som~import('animal')
The argument animal tells the SOM object the name of the class to import from SOM. The SOM object returns an object representing this SOM class and assigns it to beastclass. The class name in Object REXX will be the same as the name of the SOM class.
When Object REXX imports a class, it creates the Object REXX class with all the methods created for the SOM class, as well as the methods from the Object REXX Class class. This means you can use all the methods of the Class class and the SOM class. You cannot, however, create new methods for SOM classes from REXX. For example, the NEW method (for creating new instances of a class) is part of the Class class. You can send a NEW message to beastclass to create a new beast:
As part of its processing, the NEW method invokes the somNew method on the SOM class to create the SOM object. NEW returns this newly created object. You can use this instance of beastclass just as you would use any other Object REXX object. You can also send it any of the SOM messages appropriate for the object. The following statement, for example, returns the name of the SOM class to which the object belongs:
We don't need to worry about any conversions from Object REXX objects (Beastclass) to the real SOM object (the Animal class itself). Object REXX takes care of that for us.
Although Object REXX does interact with SOM objects, some restrictions exist, including:
- Exporting Object REXX classes to SOM.
- Using Object REXX methods from SOM (that is, you cannot export REXX methods).
For Further Adventures
Your passport to further adventures in Object Land is on this volume of The Developer Connection for OS/2 CD-ROM. The CD contains Object REXX executables, sample programs, and documentation. Object REXX is in the "Development Tools" category; view the Object REXX READ.ME file for information on getting started and for the latest technical updates.
Reprint Courtesy of International Business Machines Corporation, © International Business Machines Corporation
Reprinted from Volume 6 of The Developer Connection News