COMMENT
REDUCE INTERACTIVE LESSON NUMBER 4
David R. Stoutemyer
University of Hawaii
COMMENT This is lesson 4 of 7 REDUCE lessons. As before, please
refrain from using variables beginning with the letters F through H
during the lesson.
In theory, assignments and LET statements are sufficient to
accomplish anything that any other practical computing mechanism is
capable of doing. However, it is more convenient for some purposes
to use function procedures which can employ branched selection and
iteration as do most traditional programming languages. As a trivial
example, if we invariably wanted to replace cotangents with the
corresponding tangents, we could type;
ALGEBRAIC PROCEDURE COT(X); 1/TAN(X);
COMMENT As an example of the use of this function, we have;
COT(LOG(F));
COMMENT Note:
1. The procedure definition automatically declares the procedure
name as an operator.
2. A procedure can be executed any time after its definition,
until it is cleared.
3. Any parameters are dummy variables that are distinct from
any other variables with the same name outside the procedure
definition, and the corresponding arguments can be
arbitrary expressions.
4. The value returned by a procedure is the value of the
expression following the procedure statement.
We can replace this definition with a different one;
ALGEBRAIC PROCEDURE COT(Y); COS(Y)/SIN(Y);
G1:= COT(LOG(F));
COMMENT In place of the word ALGEBRAIC, we can optionally use the
word INTEGER when a function always returns an integer value, or we
can optionally use the word REAL when a function always returns a
floating-point value.
Try writing a procedure definition for the sine in terms of the
cosine, then type G1;
PAUSE;
COMMENT Here is a more complicated function which introduces the
notion of a conditional expression;
ALGEBRAIC PROCEDURE SUMCHECK(AJ, J, M, N, S);
COMMENT J is an indeterminate and the other parameters are
expressions. This function returns the global variable named
PROVED if the function can inductively verify that S equals the
sum of AJ for J going from M through N, returning the global
variable named UNPROVED otherwise. For the best chance of
proving a correct sum, the function should be executed under
the influence of ON EXP, ON MCD, and any other user-supplied
simplification rules relevant to the expression classes of AJ
and S;
IF SUB(J=M,AJ)-SUB(N=M,S) NEQ 0
OR S+SUB(J=N+1,AJ)-SUB(N=N+1,S) NEQ 0 THEN UNPROVED
ELSE PROVED;
ON EXP, MCD;
CLEAR X, J, N;
SUMCHECK(J, J, 1, N, N*(N+1)/2);
SUMCHECK(X**J, J, 0, N, (X**(N+1)-1)/(X-1));
COMMENT Within procedures of this sort a global variable is any
variable which is not one of the parameters, and a global variable
has the value, if any, which is current for that name at the point
from where the procedure is used. Conditional expressions have the
form
IF condition THEN expression1 ELSE expression2.
There are generally several equivalent ways of writing a conditional
expression. For example, the body of the above procedure could have
been written
IF SUB(J=M,A)-SUB(N=M,S)=0 AND S+SUB(J=N+1,A)-SUB(N=N+1,S)=0
THEN PROVED
ELSE UNPROVED.
Note how we compare a difference with 0, rather than comparing
two nonzero expressions, for reasons explained in lesson 3.
As an exercise, write a procedure analogous to SUMCHECK for proving
closed-form product formulas, then test it on the valid formula that
COS(N*X) equals the product of COS(J*X)/COS(J*X-X) for J ranging from
1 through N. You do not need to include prefatory comments
describing parameters and the returned value until you learn how to
use a text editor;
PAUSE;
COMMENT Most REDUCE statements are also expressions because they have
a value. The value is usually 0 if nothing else makes sense, but I
will mention the value only if it is useful.
The value of an assignment statement is the assigned value. Thus a
multiple assignment, performed right to left, can be achieved by a
sequence of the form
"variable1 := variable2 := ... := variableN := expression",
moreover, assignments can be inserted within ordinary expressions
such as X*(Y:=5). Such assignments must usually be parenthesized
because of the low precedence of the assignment operator, and
excessive use of this construct tends to make programs confusing.
REDUCE treats as a single expression any sequence of statements
preceded by the pair of adjacent characters << and followed by the
pair >>. The value of such a group expression is the value of the
last statement in the group.
Group expressions facilitate the implementation of tasks that are
most easily stated as a sequence of operations. However, such
sequences often utilize temporary variables to count, hold
intermediate results, etc., and it is hazardous to use global
variables for that purpose. If a top-level REDUCE statement or
another function directly or indirectly uses that variable name, then
its value or its virgin indeterminate status there might be damaged
by our use as a temporary variable. In large programs or programs
which rely on the work of others, such interference has a
nonnegligible probability, even if all programmers agree to the
convention that all such temporary variables should begin with the
function name as a prefix and all programmers attempt to comply with
the convention. For this reason, REDUCE provides another
expression-valued sequence called a BEGIN-block, which permits the
declaration of local variables that are distinct from any other
variables outside the block having the same name. Another advantage
of using local variables for temporary variables is that the perhaps
large amount of storage occupied by their values can be reclaimed
after leaving their block.
A BEGIN-block consists of the word BEGIN, followed by optional
declarations, followed by a sequence of statements, followed by the
word END. As a convenience, any text from the word END to the next
statement separator, >>, END, ELSE, or UNTIL is a comment. Within
BEGIN-blocks, it is often convenient to return control and a value
from someplace other than the end of the block rather than have the
value be that of the last statement. Consequently, control and a
value must be returned via a RETURN-statement or the form
RETURN expression
or
RETURN,
0 being returned in the latter case.
These features and others are illustrated by the following function;
PAUSE;
ALGEBRAIC PROCEDURE LIMIT(EX, INDET, PNT);
BEGIN COMMENT This function uses up through 4 iterations of
L'Hospital's rule to attempt determination of the limit of
expression EX as indeterminate INDET approaches expression
PNT. This function is intended for the case where
SUB(INDET=PNT, EX) yields 0/0, provoking a zero-divide
message. This function returns the global variable named
UNDEFINED when the limit is 0 dividing an expression which did
not simplify to 0, and this function returns the global
variable named UNKNOWN when it cannot determine the limit.
Otherwise this function returns an expression which is the
limit. For best results, this function should be executed
under the influence of ON EXP, ON MCD, and any user-supplied
simplification rules appropriate to the expression classes of
EX and PNT;
INTEGER ITERATION;
SCALAR N, D, NLIM, DLIM;
ITERATION := 0;
N := NUM(EX);
D := DEN(EX);
NLIM := SUB(INDET=PNT, N);
DLIM := SUB(INDET=PNT, D);
WHILE NLIM=0 AND DLIM=0 AND ITERATION<5 DO <<
N := DF(N, INDET);
D := DF(D, INDET);
NLIM := SUB(INDET=PNT, N);
DLIM := SUB(INDET=PNT, D);
ITERATION := ITERATION + 1 >>;
RETURN (IF NLIM=0 THEN
IF DLIM=0 THEN UNKNOWN
ELSE 0
ELSE IF DLIM=0 THEN UNDEFINED
ELSE NLIM/DLIM)
END;
% Examples follow..
PAUSE;
G1 := (E**X-1)/X;
% Evaluation at 0, causes zero divide prompt at top level, continue
% anyway.
SUB(X=0, G1);
LIMIT(G1, X, 0);
G1:= ((1-X)/LOG(X))**2;
% Evaluation at 1, causes zero divide prompt at top level, continue
% anyway.
SUB(X=1, G1);
LIMIT(G1, X, 1);
COMMENT Note:
1. The idea behind L'Hospital's rule is that as long as the
numerator and denominator are both zero at the limit point, we
can replace them by their derivatives without altering the
limit of the quotient.
2. Assignments within groups and BEGIN-blocks do not
automatically cause output.
3. Local variables are declared INTEGER, REAL, or SCALAR, the
latter corresponding to the same most general class denoted by
ALGEBRAIC in a procedure statement. All local variables are
initialized to zero, so they cannot serve as indeterminates.
Moreover, if we attempted to overcome this by clearing them,
we would clear all variables with their names.
4. We do not declare the attributes of parameters.
5. The NUM and DEN functions respectively extract the numerator
and denominator of their arguments. (With OFF MCD, the
denominator of 1+1/X would be 1.)
6. The WHILE-loop has the general form
WHILE condition DO statement.
REDUCE also has a "GO TO" statement, and using commas rather
than semicolons to prevent termination of this comment, the
above general form of a WHILE-loop is equivalent to
BEGIN GO TO TEST,
LOOP: statement,
TEST: IF condition THEN GO TO LOOP,
RETURN 0
END .
A GOTO statement is permitted only within a block, and the
GOTO statement cannot refer to a label outside the same block
or to a label inside a block that the GOTO statement is not
also within. Actually, 99.99% of REDUCE BEGIN-blocks are less
confusing if written entirely without GOTOs, and I mention
them primarily to explain WHILE-loops in terms of a more
primitive notion.
7. The LIMIT function provides a good illustration of nested
conditional expressions. Proceeding sequentially through such
nests, each ELSE clause is matched with the nearest preceding
unmatched THEN clause in the group or block. In order to help
reveal their structure, I have consistently indented nested
conditional statements, continuations of multi-line statements
and loop-bodies according to one of the many staunchly
defended indentation styles. However, older versions of REDUCE
may ruin my elegant style. If you have such a version, I
encourage you to indent nonetheless, in anticipation of a
replacement for your obsolete version. (If you have an
instructor, I also urge you to humor him by adopting his style
for the duration of the course.)
8. PL/I programmers take note: "IF ... THEN ... ELSE ..." is
regarded as one expression, and semicolons are used to
separate rather than terminate statements. Moreover, BEGIN
and END are brackets rather than statements, so a semicolon is
never needed immediately after BEGIN, and a semicolon is
necessary immediately preceding END only if the END is
intended as a labeled destination for a GOTO. Within
conditional expressions, an inappropriate semicolon after an
END, a >>, or an ELSE-clause is likely to be one of your most
prevalent mistakes.;
PAUSE;
COMMENT
The next exercise is based on the above LIMIT function:
For the sum of positive expressions AJ for J ranging from some finite
initial value to infinity, the infinite series converges if the limit
of the ratio SUB(J=J+1,AJ)/AJ is less than 1 as J approaches
infinity. The series diverges if this limit exceeds 1, and the test
is inconclusive if the limit is 1. To convert the problem to the
form required by the above LIMIT program, we can replace J by the
indeterminate 1/!*FOO in the ratio, then take the limit as !*FOO
approaches zero. (Since an indeterminate is necessary here, I picked
the weird name !*FOO to make the chance of conflict negligible)
After writing such a function to perform the ratio test, test it on
the examples AJ=J/2**J, AJ=1/J**2, AJ=2**J/J**10, and AJ=1/J. (The
first two converge and the second two diverge);
PAUSE;
COMMENT Groups or blocks can be used wherever any arbitrary
expression is allowed, including the right-hand side of a LET rule.
The need for loops with an integer index variable running from a
given initial value through a given final value by a given increment
is so prevalent that REDUCE offers a convenient special way of
accomplishing it via a FOR-loop, which has the general form
FOR index := initial STEP increment UNTIL final DO statement .
Except for the use of commas as statement separators, this construct
is equivalent to
BEGIN INTEGER index,
index := initial,
IF increment>0 THEN WHILE index <= final DO <<
statement,
index := index + increment >>
ELSE WHILE index >= final DO <<
statement,
index := index + increment >>,
RETURN 0
END .
Note:
1. The index variable is automatically declared local to the FOR-
loop.
2. "initial", "increment", and "final" must have integer values.
3. FORTRAN programmers take note: the body of the loop is not
automatically executed at least once.
4. An acceptable abbreviation for "STEP 1 UNTIL" is ":".
5. Since the WHILE-loop and the FOR-loop have implied BEGIN-
blocks, a RETURN statement within their bodies cannot transfer
control further than the point following the loops.
Another frequent need is to produce output from within a group or
block, because such output is not automatically produced. This can
be done using the WRITE-statement, which has the form
WRITE expression1, expression2, ..., expressionN.
Beginning a new line with expression1, the expressions are printed
immediately adjacent to each other, split over line boundaries if
necessary. The value of the WRITE-statement is the value of its last
expression, and any of the expressions can be a character-string
of the form "character1 character2 ... characterM" .
Inserting the word "WRITE" on a separate line before an assignment
is convenient for debugging, because the word is then easily deleted
afterward. These features and others are illustrated by the following
equation solver;
ARRAY CF(2);
OPERATOR SOLVEFOR, SOLN;
FOR ALL X, LHS, RHS LET SOLVEFOR(X, LHS, RHS) = SOLVEFOR(X, LHS-RHS);
COMMENT LHS and RHS are expressions such that P=NUM(LHS-RHS) is a
polynomial of degree at most 2 in the indeterminate or functional
form X. Otherwise an error message is printed. As a convenience,
RHS can be omitted if it is 0. If P is quadratic in X, the two
values of X which satisfy P=0 are stored as the values of the
functional forms SOLN(1) and SOLN(2). If P is a first-degree
polynomial in X, SOLN(1) is set to the one solution. If P simplifies
to 0, SOLN(1) is set to the identifier ARBITRARY. If P is an
expression which does not simplify to zero but does not contain X,
SOLN(1) is set to the identifier NONE. In all other cases, SOLN(1)
is set to the identifier UNKNOWN. The function then returns the
number of SOLN forms which were set. This function prints a well
deserved warning message if the denominator of LHS-RHS contains X.
This function also uses the global array CF as temporary storage. If
LHS-RHS is not polynomial in X, it is wise to execute this function
under the influence of ON GCD;
FOR ALL X, LHSMRHS LET SOLVEFOR(X, LHSMRHS) =
BEGIN INTEGER HIPOW; SCALAR TEMP;
IF LHSMRHS = 0 THEN <<
SOLN(1) := ARBITRARY;
RETURN 1 >>;
HIPOW := COEFF(LHSMRHS, X, CF);
IF HIPOW = 0 THEN <<
SOLN(1) := NONE;
RETURN 1 >>;
IF HIPOW > 2 THEN <<
SOLN(1) := UNKNOWN;
RETURN 1 >>;
IF HIPOW = 1 THEN <<
SOLN(1) := -CF(0)/CF(1);
IF DF(SUB(X=!*FOO, SOLN(1)), !*FOO) NEQ 0 THEN
SOLN(1) := UNKNOWN;
RETURN 1 >>;
CF(0) := CF(0)/CF(2);
CF(1) := -CF(1)/CF(2)/2;
IF DF(SUB(X=!*FOO, CF(0)), !*FOO) NEQ 0
OR DF(SUB(X=!*FOO, CF(1)), !*FOO) NEQ 0 THEN <<
SOLN(1) := UNKNOWN;
RETURN 1 >>;
TEMP := (CF(1)**2 - CF(0))**(1/2);
SOLN(1) := CF(1) + TEMP;
SOLN(2) := CF(1) - TEMP;
RETURN 2
END;
FOR K:=1:SOLVEFOR(X, A*X**2, -B*X-C) DO WRITE SOLN(K) := SOLN(K);
FOR K:=1:SOLVEFOR(LOG(X), 5*LOG(X)-7) DO WRITE SOLN(K) := SOLN(K);
FOR K:=1:SOLVEFOR(X, X, X) DO WRITE SOLN(K) := SOLN(K);
FOR K:= 1:SOLVEFOR(X, 5) DO WRITE SOLN(K) := SOLN(K);
FOR K:=1:SOLVEFOR(X, X**3+X+1) DO WRITE SOLN(K) := SOLN(K);
FOR K:=1:SOLVEFOR(X, X*E**X, 1) DO WRITE SOLN(K) := SOLN(K);
G1 := X/(E**X-1);
FOR K:=1:SOLVEFOR(X, G1) DO WRITE SOLN(K) := SOLN(K);
SUB(X=SOLN(1), G1);
LIMIT(G1, X, SOLN(1));
COMMENT Here we have used LET rules to permit the user the
convenience of omitting default arguments. (Function definitions have
to have a fixed number of parameters.)
Array elements are designated by the same syntax as matrix elements
and as functional forms having integer arguments. Here are some
desiderata that may help you decide which of these alternatives is
most appropriate for a particular application:
1. The lower bound of each array subscript is 0, vs 1 for
matrices vs unrestricted for functional forms.
2. The upper bound of each array subscript must have a specific
integer value at the time the array is declared, as must the
upper bounds of matrix subscripts when a matrix is first
referred to, on the left side of a matrix assignment. In
contrast, functional forms never require a commitment to a
specific upper bound.
3. An array can have any fixed number of subscripts, a matrix
must have exactly 2, and a functional form can have a varying
arbitrary number.
4. Matrix operations, such as transpose and inverse, are built-in
only for matrices.
5. For most implementations, access to array elements requires
time approximately proportional to the number of subscripts,
whereas access to matrix elements takes time approximately
proportional to the sum of the two subscript values, whereas
access to functional forms takes average time approximately
proportional to the number of bound functional forms having
that name.
6. Only functional forms permit the effect of a subscripted
indeterminate such as having an answer be "A(M,N) + B(3,4)".
7. All arrays, matrices, and operators are global regardless
of where they are declared, so declaring them within a BEGIN
block does not afford the protection and automatic storage
recovery of local variables. Moreover, clearing them within a
BEGIN-block will clear them globally, and functions
cannot return an array or a matrix value. Furthermore, REDUCE
parameters are value-type parameters, which means that an
assignment to a parameter has no effect on the corresponding
argument. Thus, matrix or array results cannot be transmitted
back to an argument either.
8. It is often advantageous to use two or more of these
alternatives to represent a set of quantities at different
times in the same program. For example, to get the general
form of the inverse of a 3-by-3 matrix, we could write
MATRIX AA,
OPERATOR A,
AA := MAT((0,0,0),(0,0,0),(0,0,0)),
FOR J:=1:3 DO
FOR K:=1:3 DO AA(J,K) := A(J,K),
AA**-1 .
As another example, we might use an array to receive some
polynomial coefficients, then transfer the values to a matrix
for inversion.
The COEFF function is the remaining new feature in our SOLVEFOR
example. The first argument is a polynomial expression in the
indeterminate or functional form which is the second argument, and
the third argument is a singly-subscripted array-name or an array
cross-section for receiving the polynomial coefficients of the
integer powers which correspond to their subscripts. An array
cross-section is a multiply-subscripted array-reference with an
asterisk as one subscript and specific integer values as the others.
Examples are Q(5,*) which indicates the fifth row of Q, and Q(*,5)
which indicates the fifth column of Q.
Alternatively, the third argument of COEFF can be an indeterminate,
in which case nonzero coefficients are assigned to indeterminates
with names constructed by concatenating the integer power, as a
suffix, to the given indeterminate. For example;
CLEAR C,X;
COEFF(X**5+2, X, C);
PAUSE;
COMMENT This technique is usually more convenient when COEFF is used
interactively at the top level, whereas the array technique is
usually more convenient when COEFF is used indirectly within a group
or block.
COEFF returns the highest subscript or suffix for which it made an
assignment.
COEFF does not check to make sure that the coefficients do not
contain its second argument within a functional form, so that is the
reason we differentiated. The reason we first substituted the
indeterminate !*FOO for the second argument is that differentiation
does not work with respect to a functional form.
The last exercise is to rewrite the last rule so that we can solve
equations which simplify to the form
a*x**(m+2*l) + b*x**(m+l) + c*x**m = 0, where m>=0 and l>=1.
The solutions are
0, with multiplicity m,
x1*E**(2*j*I*pi/l),
x2*E**(2*j*I*pi/l), with j = 0, 1, ..., l-1,
where x1 and x2 are the solutions to the quadratic equation
a*x**2 + b*x + c = 0 .
As a convenience to the user, you might also wish to have a global
flag named SOLVEPRINT, such that when it is nonzero, the solutions
are automatically printed.
This is the end of lesson 4. When you are ready to run lesson 5,
start a new REDUCE job.
;END;