Programming in Maxima has two very different aspcets:

- First, Maxima has its own programming language, which
can be used to extend Maxima. Important parts of Maxima are
written in that language. Code that is written in the Maxima
language is stored in files with extensions
**.mac**or**.mc**. A lot of such files can be found in the subdirectories of the directory

~/Maxima-5.9.1/share/maxima/5.9.1/share

- Maxima itself is written in Lisp. That code is available too; it
is stored in files with the extension
**.lisp**and can be found in the directory

~/Maxima-5.9.1/share/maxima/5.9.1/src

The use of the Maxima programming language is highly recommended for serious work. Programming with Lisp is work for advanced users. Occassionally, it may be necessary to use both programming languages to solve a problem. The use of files is a candidate for bilingual programming.

A simple Maxima program is nothing but a sequence of Maxima commands. To be able to reproduce a lengthy computation at a later time, it is often prudent to record its commands in a batch file. Such a protocol file is in fact a Maxima program.

In the chapter about Tubes around Spacial Curves we used this definition to compute the tangent of a spacial curve:

tangent(fn, x) := diff(fn(x), x) / ((diff(fn(x), x) . diff(fn (x), x))^(1/2))$

This is a straightforward definition, but it has a serious flaw:
It computes the derivative of **f** three times. It would be much
better to compute the derivative only once and to store it for later
use.

tangent(fn, x) := block ( [df: diff(fn(x), x)], df / (df . df)^(1/2) )$

Explanations:

- A
**block**is more or less what a procedure body is in languages like Pascal or Ada. It contains a list of local variables and one or several expressions. The names of local variables are case sensitive. - The declaration
[df: diff(fn(x), x)]

introduces the temporary variable**df**and assigns it the value of**diff(fn(x), x)**as initial value. - The expression
df / (df . df)^(1/2)

uses the value of**df**to compute the tangent vector. This expression is the last expression of the block; its value is answered as the result of the block evaluation.

An expression is either an atom or a structure that is built with an
operator and a list of arguments. The function **op** is used to
access the operator of an expression, the function **args** is
used to obtain a list of all arguments. Some examples explain this:

op (a + b + c); + args(a + b + c); [c, b, a] op (sin(3*x)); sin args (sin(3*x)); [3 x] op (f(a, b, g(c), d)); f args (f(a, b, g(c), d)); [a, b, g(c), d]

Note that **exp(x)** is translated into a power to the constant **%e**:

op(exp(2*x)); ^ args(exp(2*x)); [%e, 2 x]

A non-atomar expression contains always exactly one operator and a collection of arguments that are in turn expressions. An expression is thus a tree. It is sometimes helpful to traverse that tree and to collect all operators. The knowledge of all used operators can be used to select promising simplifications and to skip simplifications that are obviously not needed in the absence of some operators.

This section explains how such a function is programmed.

We want a function that takes an expression and answers a list with all operators of the expression:

allOps(a); [] allOps(sin(x) + cos(x)); [cos, sin, +] allOps(sin(x) + 2*sin(2*x)); [sin, *, +]

The answered list should contain every found operator once. Repetions are not desired. The last expression exemplifies this requirement.

The function **allOps** is very simple: It delegates the
traversal of the expression to a function that takes a subexpression
and a list of operators that were found so far:

allOps(expression):= block( [ ], allOpsPriv (expression, []) );

The function **allOpsPriv** requires more work.
A first attempt handles only the simple cases:

/* incomplete preliminary version */allOpsPriv(expression, opList) := block ( [x], if atom(expression) then opList else (x: op(expression), cons(x, opList) ) );

This definition handles two simple cases:

- For an atomar expression the function answers the unchanged list of known operators. This is correct because an atomar expression has no operator.
- For a nonatomar expression the function
**cons**is used to add the operator of that expression to the list of known operators. Subexpressions are not processed, this is the topic of the next refinement of the algorithm.

The definition uses an if-statement to identify and to
handle both cases. An if-statement always answers the
value of its evaluated alternative as its result. For
an atomar expression, the above if-statement answers the
value of **opList**. The else-branch of the if-statement
contains two statements that are written in parentheses.
The result of **cons(x, opList)** is the result of the
entire if-statement when the else-branch is selected for
evaluation.

Note that the definition of **allOpsPriv** uses a
temporary variable to store the operator of a nonatomar
expression. This is not really necessary, it is also
possible to write:

cons(op(expression), opList)

When we try the definition that we have now, we obtain:

allOps(a); [] allOps(sin(x) + cos(x)); [+]

The inspection of the subexpressions is obviously missing. For a complete solution we have to:

- fetch the list of arguments of a nonatomar expression
- process all expressions in the argument list

A refinement of this definition requires additional actions in the else-branch of the if-statement.

Added code is displayed in bold:

allOpsPriv(expression, opList) := block ( [x, args, newList], if atom(expression) then opList else (x: op(expression),args: args(expression), newList:cons(x, opList),for arg in args do newList: allOpsPriv(arg, newList), newList) );

Here we use the language element

for arg in args do<statement>

which iterates over all elements

A quick test reveals both remarkable improvements and an imperfection of the algorithm:

allOps(a); [] allOps(sin(x) + cos(x)); [cos, sin, +] allOps(sin(x) + 2*sin(2*x)); [sin, *, sin, *, +]

The operators **sin** and ***** are mentioned twice. This is not
really a surprise, since we do not check whether an operator is already in the list
before we add it. It is however easy to fix that:

allOpsPriv(expression, opList) := block ( [x, args, newList], if atom(expression) then opList else (x: op(expression), args: args(expression), newList:if member (x, opList) then opList elsecons(x, opList), for arg in args do newList: allOpsPriv(arg, newList), newList ) );

You find this code in file allOps.mc. When you
copy that file into the subdirectory **user** of your Maxima installation,
you can load it with

batch("allOps");

The function **allOps** collects all operators of an expression. To do that,
it has to completely traverse an expression. For a similar problem complete
traversal is not allways needed:

When we are looking for the presence of a given operator in an expression, we can stop traversal as soon as we have found the operator we are looking for.

containsOp(expression, opList) := block ( [x, args,found], if atom(expression) thenfalseelse (x: op(expression), args: args(expression), if member (x, opList) thentrueelse (found : false,for arg in argswhile not founddo found: containsOp(arg, opList), found) ) );

Here we use a variant of the earlier used language element **for arg in args do**:

for arg in args while not found do<statement>

which iterates over the elements