A Look at OREXX
Written by Gerald Goertzel
Why Object Rexx (ORexx)?
I like Rexx. I do most of my programming with Rexx. Rexx has some deficiencies. Some of these deficiencies are cured in ORexx. This is one reason for my using ORexx. There is little cost in going to ORexx since nearly all the old Rexx programs work unchanged. Even without explicit use of objects, ORexx is a better Rexx than Rexx. For example, stem variables can be used as arguments to routines and can be returned from routines.
My major reason for trying Orexx is to create objects which will enable me to program faster and better. I don't have use for many objects, but the ones I want are important to me. In my work I am concerned with matrices, polynomials, vectors, and complex numbers. These mathematical constructs are ideally suited for representation as objects.
In this article I shall illustrate the creation of objects with one example, the polynomial object. I assume the reader has a reasonable knowledge of Rexx, but I do not assume any previous acquaintance with object oriented programming or with ORexx.
I created a polynomial class of objects by writing the routine poly.cmd. In order to have polynomials available as an integral part of my REXX system I merely need to ensure that poly.cmd is in the path.
The main part of this article is a commentary on poly.cmd, introducing concepts (not very many) from ORexx as needed. Prior to that, however, I will say something about polynomials. Indubitably, most of the readers have heard of polynomials and have studied them in school, but have forgotten all about them.
Carlo Evangelisti studied this paper and made many suggestions for improvement.
A polynomial of degree n is defined as:
a*x^n + a*x^(n-1) + ... + a[n+1]Figure 1: The general form of a polynomial.
The "^" denotes to the power of so that x^3 means the third power of x or x*x*x. a to a[n+1] are the coefficients of the polynomial.
Suppose p and q have been defined as two specific polynomials. Then we would like to display the polynomial p by executing "say p". Similarly, we would like to be able to write "p+q", "p-q", "p*q", "p/q", and "p//q" to add, subtract, multiply and divide the polynomials. "p//q" denotes the remainder when p is divided by q, whereas "p/q" is the quotient. Here are some examples of these operations. They are best viewed with a fixed spacing font.
p 1*x^4 + 2*x^3 + 3*x^2 + 4*x + 5 q 10*x^2 + 20*x + 3 --------------------------------- p+q 1*x^4 + 2*x^3 + 13*x^2 + 24*x + 8Figure 2: Example of polynomial addition.
p 1*x^4 + 2*x^3 + 3*x^2 + 4*x + 5 q 10*x^2 + 20*x + 3 --------------------------------- p-q 1*x^4 + 2*x^3 - 7*x^2 - 16*x + 2Figure 3: Example of polynomial subtraction.
Polynomial multiplication of p by q
p 2*x^2 + 3*x + 4 q 5*x + 7 ----------------- p*5*x 10*x^3 + 15*x^2 + 20*x p*7 14*x^2 + 21*x + 28 --------------------------- p*(5*x+7) 10*x^3 + 29*x^2 + 41*x + 28Figure 4: Example of polynomial multiplication.
Polynomial division of n/d
n=1*x^4 + 5*x^3 + 7*x^2 + 9*x + 3 d=1*x^2 + 2*x + 3Figure 5: Example of polynomial division.
We find a quotient q and a remainder r such that n=q*d+r.
q (calculated) 1*x^2 + 3*x - 2 -------------------------------- 1*x^2 + 2*x + 3 ) 1*x^4 + 5*x^3 + 7*x^2 + 9*x + 3 d*x^2 1*x^4 + 2*x^3 + 3*x^2 ------------------------------- 3*x^3 + 4*x^2 + 9*x + 3 d*3*x 3*x^3 + 6*x^2 + 9*x ----------------------- -2*x^2 + 3 d*-2 -2*x^2 - 4*x - 6 ---------------- r (calculated) 4*x + 9Figure 6: Polynomial division, fully expressed.
Creating a polynomial
To create a polynomial "p" with coefficients 1,2,5, and -3, the clause "p=poly(1,2,5,-3)" is executed. This polynomial may be displayed by the statement "say p". The resultant display is
+ 1*x^3 + 2*x^2 + 5*x - 3Figure 7: Displaying a polynomial.
The code necessary to accomplish this is in poly.cmd. This code will now be described in detail. I first display a program fragment and then comment on the fragment.
The following code creates a polynomial and initializes it. To create a polynomial p with coefficients c1, c1, ... , cn we execute the clause "p=poly(c1,c2,...,cn)".
/* poly.cmd */ p=.poly~new(arg()) do i=1 to arg() p[i]=arg(i) end return p ::class poly ::method init expose coeff count parse arg count coeff=.array~new(count) ::method '=' expose coeff parse arg x,i coeff[i]=xFigure 8: POLY.CMD
In general, the clause:
INSTANCE=.CLASS_NAME~new(ARGUMENTS)Figure 9: Creating a new instance of a class.
creates a new object INSTANCE in the class .CLASS_NAME. INSTANCE points to a "variable pool" for the object. This pool contains data which can be known to all methods which may be applied to INSTANCE. To get at data in the variable pool, a method must contain, as its first clause, an "expose" clause which designates the data to be accessed. If the class has an init method defined, this method is called to initialize the new object. This new object is an "instance" of the class .CLASS_NAME.
The clause "p=.poly~new(arg())" in the code above causes two actions: 1) p is set to point to the variable pool for the polynomial p and 2) the method init of the poly class is invoked with the argument arg(). "arg()" is the number of arguments present in the invocation of poly.cmd and thus designates the number of coefficients (1+the degree) of the polynomial.
In the code for the method init the first clause "expose coeff count" makes the variables coeff and count in the variable pool available to the method. "parse arg count" sets the value of count to that passed to the method init.
The clause "coeff=.array~new(count)" creates the one dimensional array coeff. The array class is built into ORexx. All methods of the class apply to coeff. In particular coeff[i] denotes the i'th element of the array coeff and the clause "coeff[i]=X" sets the i'th element of coeff to X.
We have seen how the init method is invoked when an instance of our new class is created. To apply a method to an instance, we send a message to the instance as a receiver using a clause like
RECEIVER~MESSAGE(arguments)Figure 10: Invoking a method of an object.
where MESSAGE is the name of a method which is defined in the class of which RECEIVER is an instance.
When the method desired corresponds to an operator of Rexx (such as "+"), an alternate method of invoking the method may be used. As an example, consider the process of adding X to NAME. This could be accomplished with a method '+'. This method would be invoked by the clause "NAME~'+'(X)". ORexx permits us to write an equivalent clause "NAME+X". Note that the method invoked is determined by the class of NAME, not by the class of X.
The do loop in poly.cmd sets the initial values of the coefficients of p to the values that were passed as arguments to poly.cmd. To accomplish this we might have written, in the do loop, the clause "p~'='(arg(i),i)" to invoke the method '='. ORexx accepts p[i]=arg(i) as equivalent to the previous clause. The method is defined in terms of the corresponding method of the array class.
If the poly class had no more methods, we could create a polynomial but could not do anything with it. I will now describe, one at a time, additional methods that are added to the class to make it useful.
Displaying a polynomial
We need a means to find the value of a coefficient of a polynomial p. The clause "x=p[i]" will invoke the method '' with the argument i. The method will then return the value of the i'th coefficient of p.
::method '' expose coeff parse arg i return coeff[i]Figure 11: Implementing the bracket operator to return a coefficient.
We still are not able to display all the coefficients of p. We need a method to tell us the number of coefficients of p or, better, the degree of p.
::method degree expose count return count-1Figure 12: Determining the number of coefficients.
"p~degree" returns the degree of the polynomial p. It would now be elementary to display p as a list of its coefficients or to define a cmd polyfmt which would display p in a desired format.
When the keyword say is used to display an object, the string method of that object (if any) is invoked. We can arrange to display polynomials in our desired format by defining a string method for the polynomial class. If a string method is not defined for a polynomial p, say p would result in the display of "a POLY".
::method string return polyfmt(self)Figure 13: Invoking the display method.
Here polyfmt is an external routine. It is shown in the appendix. We would like to call polyfmt with the argument p, but p is unknown inside the method. However, for any method, "self" is an alias for the object for which the method was called. The result returned by p~string is the same as that from polyfmt(p).
The hard work is now mostly behind us, at least as far as the use of ORexx is concerned. We now consider the properties of polynomials.
To add two polynomials, we merely have to add the coefficients of corresponding powers of x. The result is a polynomial of degree equal to the larger degree of the addends.
::method '+' numeric digits 16 use arg x if datatype(x)='NUM' then x=poly(x) cx=x~degree+1 cs=self~degree+1 cp=max(cx,cs) p=.poly~new(cp) do i=1 to cp p[i]=0 end do i=cp-cs+1 to cp p[i]=p[i]+self[cs-cp+i] end do i=cp-cx+1 to cp p[i]=p[i]+x[cx-cp+i] end return p~reduceFigure 14: Addition of two polynomials.
The clause "use arg x" would be more familiar as "parse arg x". In conventional Rexx an argument passed to a routine is read by the clause "parse arg x". x will always contain a string. In ORexx we need to be able to use objects as arguments. Objects are not necessarily strings. "use arg x" results in x containing a pointer to the object passed as an argument.
We are adding x to self. x may be a number or a polynomial. If x is a number, as determined by the function datatype, we replace it with a 0 degree polynomial to avoid handling a special case.
The method degree is used to find the number of coefficients of x, cx, and to find the number of coefficients of self, cs. The output polynomial, p, has cp coefficients, where cp is the larger of cx and cs. "p=.poly~new(cp)" creates the polynomial p.
The first do loop sets all the coefficients of p to zero. The second loop adds the coefficients of self to the coefficients of the corresponding powers of p. Similarly the third loop adds the coefficients of x.
"p=self+x" has been evaluated. It may so happen that the leading coefficient of p is zero. In this case the degree of p should be reduced. Thus we apply the method reduce to p before returning the result.
::method reduce /* remove leading 0 terms */ numeric digits 16 if abs(self)>1e-9 then return self d=self~degree do i=2 to d if abs(self[i])>1e-9 then leave end x=.poly~new(d+2-i) do k=i to d+1 x[k-i+1]=self[k] end return xFigure 15: Reducing a polynomial.
If the leading coefficient (self) is not zero, self is returned unchanged. The first do loop calculates the number of leading zeros as i-1 so that the output polynomial x must have degree d-(i-1) and therefore d+2-i coefficients. The second do loop copies the relevant coefficients from self to x.
::method '*' expose coeff count numeric digits 16 use arg q if word(q~class,2)\='POLY' then q=poly(q) dp=count dq=q~degree+1 dr=dp+dq-1 r=.poly~new(dr) do i=1 to dr r[i]=0 end do i=1 to dp do j=1 to dq k=i+j-1 r[k]=r[k]+coeff[i]*q[j] end end return rFigure 16: Multiplying two polynomials.
The degree of the product is the sum of the degrees of the polynomials being multiplied. Note that we first set the coefficients of the result, r, equal to zero. We then multiply the two polynomials term by term, adding each product to the correct coefficient of r. Note that in this method, I have chosen to expose count and coeff and to use coeff[i] rather than self[i]. This is a somewhat faster approach.
We now consider '-'. The symbol denotes negation (a unary operation) or subtraction. We could model subtraction after addition, mutatis mutandis. The approach taken here is another way of doing it.
::method '-' numeric digits 16 use arg x if arg(1,'omitted') then return self*-1 else return self+x*-1Figure 17: Subtracting two polynomials.
For a null argument, "use arg x" acts differently than "parse arg x". The test for a missing n'th argument is arg(n,'omitted'). The 'omitted' may be abbreviated to 'o'. Note that we have defined subtraction of x as addition of -x.
Division: "/" and "//"
Given two polynomials, I occasionally need the quotient, the remainder, or both when dividing one of the given polynomials by the other. The method '/' returns the quotient, the method '//' the remainder.
::method '/' numeric digits 16 num=self~copy use arg den if datatype(den)='NUM' then den=poly(den) dnum=num~degree+1 dden=den~degree+1 dquot=dnum-dden+1 if dquot<1 then return poly(0) quot=.poly~new(dquot) do i=1 to dquot quot[i]=num[i]/den do j=1 to dden k=j+i-1 num[k]=num[k]-quot[i]*den[j] end end return quotFigure 18: Dividing two polynomials.
If the third line were "num=self" the division would come out right, but self would be changed. This is an undesirable side effect. The code makes a copy of self and works with the copy. The copy method is found in the code in the appendix. The algorithm used here is shown, for a specific case, in the example of Polynomial division shown early in this article.
::method '//' numeric digits 16 num=self use arg den quot=num/den rem=num-den*quot return rem~reduceFigure 19: Integer division.
This method calculates the remainder directly from its definition.
The code for poly.cmd includes, in addition to the methods described above, methods to find the value of a polynomial for a given value of x, to find the greatest common factor of a polynomial, and to find the derivative with respect to x of a polynomial. I also have a method to find all the roots of a polynomial, but that is beyond the scope of this article.