This document is intended to be a tutorial introduction to the AXIOM
(Release 2.0) system and its language. Initially the examples and
discussion will relate to the interactive system and the use of input
files will be touched on later.

Although
[jenks1992]
is an extremely useful book for those wanting to use the AXIOM
system it is not quite so helpful for learning to program in AXIOM.
Generally one learns a new programming language by writing short programs
("one-liners") before learning about the various flow-control
instructions such as if and while. However, learning to
use a large system such as AXIOM requires a text that covers a
significant part of the available features while keeping the number of
pages to a minimum as
[jenks1992]
does. This is not to say that
[jenks1992]
does not explain how to program in AXIOM since this is how the
author learned!

As a result the author has decided to produce this text as an introduction to
the AXIOM language for readers who are already familiar with at least
one programming language. Knowledge of a functional programming language
would be a benefit since many of the concepts found in functional languages
are present in AXIOM but is by no means essential. AXIOM is
not a functional language since it has a notion of state and
functions can (and sometimes do) have side-effects.

This document has been compiled by working through
[jenks1992]
in parallel with interactive sessions of AXIOM (a new session is
started for each chapter). The structure of the text is the author's but
the majority of the information has come from
[jenks1992]
in some form or another since this appears to be the
only large body of information on AXIOM (apart from the HyperDoc
program). However, the examples are mostly of the author's own design
and are all extracted directly from the output produced by AXIOM - the
code for each example was copied from the LaTeX version of this document
into the AXIOM interpreter and the results copied back. As a result
there shouldn't be any mistakes.

Please let me
(mnd@dcs.st-andrews.ac.uk)
know of any errors or any comments/criticisms you have regarding this
document as I would like to make it as useful as possible.

At the simplest level AXIOM can be used as a pocket calculator where
expressions involving numbers and operators are entered directly in infix
notation. In this sense the more advanced features of the calculator can
be regarded as operators (e.g. sin, cos etc.).

An example of this might be to calculate the cosine of 2.45 (in radians).
To do this one would type:

(1) -> cos 2.45
(1) -0.7702312540 473073417
Type: Float

Before proceeding any further it would be best to explain the previous
three lines. Firstly the text ``(1) -> '' is part of the prompt
that the AXIOM system provides when in interactive mode. The full prompt
probably has other text preceding this but it is not relevent here. The
number in parenthesis is the step number of the input which may be used
to refer to the results of previous calculations. The step number
appears at the start of the second line to tell you which step the result
belongs to. Since the interpreter probably loaded numerous libraries to
calculate the result given above and listed each one in the process, there
could easily be several pages of text between your input and the answer.

The last line contains the type of the result. The type Float
is used to represent real numbers of arbitrary size and precision (where
the user is able to define how big arbitrary is - the default is 20 digits
but can be as large as your computer system can handle). The type of the
result can help track down mistakes in your input if you don't get the
answer you expected.

Other arithmetic operations such as addition, subtraction, multiplication
behave as expected:

but integer division isn't quite so obvious. For example, if one types:

(4) -> 4/6
2
(4) -
3
Type: Fraction Integer

a fractional result is obtained. The function used to display fractions
attempts to produce the most readable answer. In the example:

(5) -> 4/2
(5) 2
Type: Fraction Integer

the result is stored as the faction 2/1 but is displayed as
the integer 2. This fraction could be converted to type
Integer with no loss of information AXIOM will not
do so automatically.

To obtain the floating point value of a fraction one must convert
(according to the documentation, coercions are conversions that
are automatically applied by the kernel) it to type Float
using the ``::'' operator (see
Section 4.5)
as follows (the parentheses are required due to the relative
precedences of ``::'' and ``/''):

Although AXIOM can convert this back to a fraction it might
not be the same fraction it started as due to rounding errors. For
example, the following conversion appears to be without error but
others might not:

where ``%'' represents the previous result (not the
calculation).

Although AXIOM has the ability to work with floating-point numbers
to very high precision it must be remembered that calculations
with these numbers are not exact. Since AXIOM is a computer
algebra package and not a numerical solutions package this should
not create too many problems. The idea is that the user should
use AXIOM to do all the necessary symbolic manipulation and only
at the end should actual numerical results be extracted.

If you bear in mind that AXIOM appears to store expressions just as
you have typed them and does not perform any evaluation of them unless
forced to then programming in the system will be much easier. It means
that anything you ask AXIOM to do (within reason) will be carried
with complete accuracy.

In the previous examples the ``::'' operator was used to
convert values from one type to another. This type conversion is
not possible for all values for instance, it is not possible to
convert the number 3.4 to an integer type since it can't be
represented as an integer. The number 4.0 however can be converted
to an integer type since it has no fractional part. Refer to
Section 4.5)
for more details on type conversion.

Conversion from floating point values to integers is performed using
the functions round and truncate.
The first of these rounds a floating point number to the nearest
integer while the other truncates i.e. removes the fractional part.
Both functions return the result as a floating point number.
To extract the fractional part of a floating point number use the
function fractionPart but note that the sign of
the result depends on the sign of the argument (AXIOM obtains
the fractional part of ``x'' using ``x - truncate x''):

To obtain the absolute value of a number the abs function
can be used. This takes a single argument which is usually an integer or
a floating point value but doesn't necessarily have to be. The sign of
a value can be obtained via the sign function which
returns -1, 0, or 1 depending on the sign of the argument:

Tests on values can be done using using various functions which
are generally more efficient than using relational operators such as
``='' particularly if the value is a matrix. Examples
of some of these functions are:

Some other functions that are quite useful for manipulating numerical
values are shown in
Figure 2.1
while
Figure 2.2
contains a table of simple infix and prefix operators.
Figure 2.3
is a table of predefined macros that can be used
in various situations.

In the previous section all the examples involved numbers and simple
functions. Also none of the expressions entered were assigned to anything.
In this section we will move on to simple algebra i.e. expressions
involving symbols and other features available on more sophisticated
calculators.

where the assignment operator ``:='' represents
immediate assignment. Later it will be seen that this form of assignment
is not always desirable and the use of the delayed assignment operator
``=='' will be introduced. The type of the result is
Polynomial Integer which is used to represent polynomials
with integer coefficients. Some other examples along similar lines are:

Given that we can define expressions involving symbols, how do we actually
compute the result when the symbols are assigned values? The answer is to
use the eval function which takes an expression as it's
first argument followed by a list of assignments. For example, to evaluate
the expressions xDummy and xyDummy resulting
from their respective assignments above we type:

For many scientific calculations real numbers aren't sufficient and
support for complex numbers is also required. Complex numbers are
handled in an intuitive manner by AXIOM which uses the %i
macro to represent i, the square root of -1. Thus expressions
involving complex numbers are entered just like other expressions:

The real and imaginary parts of a complex number can be extracted using
the real and imag functions and the
complex conjugate of a number can be obtained using conjugate:

By default all numerical results are displayed in decimal with real
numbers shown to 20 significant figures. If the integer part of a
number is longer than 20 digits then nothing after the decimal point
is shown and the integer part is given in full. To alter the number
of digits shown the function digits can be called. The result
returned by this function is the previous setting. For example, to
find the value of pi to 40 digits we type:

As can be seen in the example above, there is a gap after every ten
digits. This can be changed using the outputSpacing
function where the argument is the number of digits to be displayed
before a space is inserted. If no spaces are desired then use the
value 0. Two other functions controlling the appearance of real
numbers are outputFloating and
outputFixed. The former causes AXIOM to display
floating-point values in exponent notation and the latter causes
it to use fixed point notation. For example:

Note that the semicolon ``;'' in the examples above allows several
expressions to be entered on one line. The result of the last expression
is displayed. Remember also that the percent symbol ``%'' is used
to represent the result of a previous calculation.

To display rational numbers in a base other than 10 the function
radix is used. The first argument of this function is
the expression to be displayed and the second is the base to be used:

Rational numbers can be represented as a repeated decimal expansion
using the decimal function or as a continued fraction
using continuedFraction. Any attempt to call these
functions with irrational values will fail.

Finally, partial fractions in compact and expanded form are available
via the functions partialFraction and
padicFraction respectively. The former takes two
arguments, the first being the numerator of the fraction and the second
being the denominator. The latter function takes a fraction and
expands it further while the function compactFraction
does the reverse:

To extract parts of a partial fraction the function
nthFractionalTerm is available and returns a partial
fraction of one term. To decompose this further the numerator can
be obtained using firstNumer and the denominator with
firstDenom. The whole part of a partial fraction can
be retrieved using wholePart and the number of fractional
parts can be found using the function
numberOfFractionalTerms:

By using the type constructor PrimeField? it is possible
to do arithmetic modulo some prime number. For example, arithmetic
modulo 7 can be performed as follows:

``Let x be of type PrimeField? 7 and
assign to it the value 5.''

Note that it is only possible to invert non-zero values if the
arithmetic is performed modulo a prime number. Thus arithmetic
modulo a non-prime integer is possible but the reciprocal operation
is undefined and will generate an error. Attempting to use the
PrimeField? type constructor with a non-prime
argument will generate an error. An example of non-prime
modulo arithmetic is:

It is sometimes desirable to enter an expression and prevent AXIOM
from displaying the result. To do this the expression should be
terminated with a semicolon ``;''. In
Section 3.3
it was mentioned that a set of expressions separated by semicolons
would be evaluated and the result of the last one displayed. Thus
if a single expression is followed by a semicolon no output will
be produced (except for its type):

As mentioned in previous sections the ``%'' macro
represents the result of the previous computation and some other
macros were given in
Figure 2.3.
To refer to earlier calculations
the ``%%'' macro is available which takes a single
integer argument. If the argument is positive then it refers to
the step number of the calculation where the numbering begins
from one and can be seen at the end of each prompt (the number in
parentheses). If the argument is negative then it refers to
previous results counting backwards from the last result
i.e. ``%%(-1)'' is the same as ``%''. The value
of ``%%(0)'' is not defined and will generate an error
if requested.

Although AXIOM will quite happily accept expressions that are
longer than the width of the screen (just keep typing without
pressing the Return key) it is often preferable to split the
expression being entered at a point where it would result in
more readable input. To do this the underscore ``_'' symbol
is placed before the break point and then the Return key pressed.
The rest of the expression is typed on the next line and can be
preceded by any number of whitespace characters, for example:

(2) -> 2_
+_
3
(2) 5
Type: PositiveInteger

The underscore symbol is an escape character and its presence alters
the meaning of the characters that follow it. As mentioned above
whitespace following an underscore is ignored (the Return key
generates a whitespace character). Any other character following
an underscore loses whatever special meaning it may have had. Thus
one can create the identifier ``a+b'' by typing
``a_+b'' although this might lead to confusions. Also
note the result of the following example:

Comments and descriptions are really only of use in files of AXIOM
code but can be used when the output of an interactive session is
being spooled to a file (via the system command )spool).
A comment begins with two dashes ``--'' and continues until
the end of the line. Multi-line comments are only possible if each
individual line begins with two dashes.

Descriptions are the same as comments except that the AXIOM compiler
will include them in the object files produced and make them available
to the user for documentation purposes. For more details on this refer
to [watt1995].

A description placed before a calculation begins with three plus
symbols ``+++'' and a description placed after a calculation
begins with two plus symbols ``++''.

This section needs to be rewritten and extended to give a proper
description of converting values from one type to another.

In earlier sections the type of an expression was converted to another
via the ``::'' operator. However, this is not the only method
for converting between types and two other operators need to be
introduced and explained.

The first operator is ``$'' and is used to specify the
package to be used to calculate the result. Thus:

tells AXIOM to use the ``/'' operator from the
Float package to evaluate the expression 2/3. This does not
necessarily mean that the result will be of the same type as the
domain from which the operator was taken. In the following example
the sign operator is taken from the Float package
but the result is of type Integer.

(5) -> sign(2.3)$Float
(5) 1
Type: Integer

The other operator is ``@'' which is used to tell AXIOM
what the desired type of the result of the calculation is. In most
situations all three operators yield the same results but the example
below should help to distinguish them.

(6) -> (2 + 3)::String
(6) "5"
Type: String
(7) -> (2 + 3)@String
An expression involving @ String actually evaluated to
one of type PositiveInteger . Perhaps you should use
:: String .
(7) -> (2 + 3)$@String
The function + is not implemented in String .

If an expression X is converted using one of the three operators to
type T the interpretations are follows:

::

means explicitly convert X to type T if possible.

$

means use the available operators for type T to compute X.

@

means choose operators to compute X so that the result
is of type T.

The AXIOMList type constructor is used to create
homogenous lists of finite size. The notation for lists and the
names of the functions that operate over them are similar to those
found in functional languages such as ML.

Lists can be created by placing a comma separated list of values
inside square brackets or if a list with just one element is desired
then the function list is available:

(1) -> [4]
(1) [4]
Type: List PositiveInteger
(2) -> list(4)
(2) [4]
Type: List PositiveInteger
(3) -> [1,2,3,5,7,11]
(3) [1,2,3,5,7,11]
Type: List PositiveInteger

The function append takes two lists as arguments and
returns the list consisting of the second argument appended to the first.
A single element can be added to the front of a list using
cons:

(4) -> append([1,2,3,5],[7,11])
(4) [1,2,3,5,7,11]
Type: List PositiveInteger
(5) -> cons(23,[65,42,19])
(5) [23,65,42,19]
Type: List PositiveInteger

Lists are accessed sequentially so if AXIOM is asked for the value
of the twentieth element in the list it will move from the start of the
list over nineteen elements before it reaches the desired element. Each
element of a list is stored as a node consisting of the value of the
element and a pointer to the rest of the list. As a result the two main
operations on a list are called first and
rest. Both of these functions take a second optional
argument which specifies the length of the first part of the list:

Other functions are empty? which tests to see if a list
contains no elements, member? which tests to see if the
first argument is a member of the second, reverse
which reverses the order of the list, sort which sorts
a list and removeDuplicates which removes any duplicates.
The length of a list can be obtained using the ``#'' operator.

Lists in AXIOM are mutable and so their contents (the elements and
the links) can be modified in situ. Functions that operate
over lists in this way have names ending with the symbol ``!''.
For example, concat! takes two lists as arguments
and appends the second argument to the first (except when the first
argument is an empty list) and setrest! changes the link emanating
from the first argument to point to the second argument (see
Figure 5.1
and
Figure 5.2
for a graphical explanation of the following example of setrest!):

(16) -> u := [9,2,4,7]
(16) [9,2,4,7]
Type: List PositiveInteger
(17) -> concat!(u,[1,5,42]); u
(17) [9,2,4,7,1,5,42]
Type: List PositiveInteger
(18) -> endOfu := rest(u,4)
(18) [1,5,42]
Type: List PositiveInteger
(19) -> partOfu := rest(u,2)
(19) [4,7,1,5,42]
Type: List PositiveInteger
(20) -> setrest!(endOfu,partOfu); u
-----
(20) [9,2,4,7,1]
Type: List PositiveInteger

From this it can be seen that the lists returned by first
and rest are pointers to the original list and not a
copy. Thus great care must be taken when dealing with lists in AXIOM.

Although the nth element of a list l can be obtained by applying
the first function to n-1 applications of
rest to l, AXIOM provides a more useful access
method in the form of the ``.'' operator:

The operation u.i is referred to as indexing into u or
elting into u. The latter term comes from the elt
function which is used to extract elements (the first element of the
list is at index 1).

(27) -> elt(u,4) -- Same as u.4
(27) 7
Type: PositiveInteger

If a list has no cycles then any attempt to access an element beyond
the end of the list will generate an error. However, in the example above
there was a cycle starting at the third element so the access to the
sixth element wrapped around to give the third element. Since lists are
mutable it is possible to modify elements directly:

(28) -> u.3 := 42; u
------
(28) [9,2,42,7,1]
Type: List PositiveInteger

Using the ``#'' operator on a list with cycles causes
AXIOM to enter an infinite loop.

Note that any operation on a list L that returns a list
L' will, in general, be such that any changes to L' will have
the side-effect of altering L. For example:

(33) -> m := rest(L,2)
(33) [4,7]
Type: List PositiveInteger
(34) -> m.1 := 20; L
(34) [9,3,20,7]
Type: List PositiveInteger
(35) -> n := L
(35) [9,3,20,7]
Type: List PositiveInteger
(36) -> n.2 := 99; L
(36) [9,99,20,7]
Type: List PositiveInteger
(37) -> n
(37) [9,99,20,7]
Type: List PositiveInteger

Thus the only safe way of copying lists is to copy each
element from one to another and not use the assignment operator:

(38) -> p := [i for i in n] -- Same as `p := copy(n)'
(38) [9,99,20,7]
Type: List PositiveInteger
(39) -> p.2 := 5; p
(39) [9,5,20,7]
Type: List PositiveInteger
(40) -> n
(40) [9,99,20,7]
Type: List PositiveInteger

In the previous example a new way of constructing lists was given.
This is a powerful method which gives the reader more information
about the contents of the list than before and which is extremely
flexible. The example,

(41) -> [i for i in 1..10]
(41) [1,2,3,4,5,6,7,8,9,10]
Type: List PositiveInteger

should be read as

``Using the expression i, generate each element of the list by
iterating the symbol i over the range of integers [1,10]?.''

To generate the list of the squares of the first ten elements we
just use:

(42) -> [i^2 for i in 1..10]
(42) [1,4,9,16,25,36,49,64,81,100]
Type: List PositiveInteger

For more complex lists we can apply a condition to the elements that
are to be placed into the list thus to obtain a list of even numbers
between 0 and 11:

(43) -> [i for i in 1..10 | even?(i)]
(43) [2,4,6,8,10]
Type: List PositiveInteger

This example should be read as

``Using the expression i, generate each element of the list by
iterating the symbol i over the range of integers [1,10]? such that
i is even.''

The following achieves the same result:

(44) -> [i for i in 2..10 by 2]
(44) [2,4,6,8,10]
Type: List PositiveInteger

A segmented list is one in which some of the elements are ranges of
values. The expand function converts lists of this
type into ordinary lists:

(45) -> [1..10]
(45) [1..10]
Type: List Segment PositiveInteger
(46) -> [1..3,5,6,8..10]
(46) [1..3,5..5,6..6,8..10]
Type: List Segment PositiveInteger
(47) -> expand(%)
(47) [1,2,3,5,6,8,9,10]
Type: List Integer

If the upper bound of a segment is omitted then a different type of
segmented list is obtained and expanding it will produce a stream (which
will be considered in the next section):

Streams are infinite lists which have the ability to calculate the
next element should it be required. For example, a stream of positive
integers and a list of prime numbers can be generated by:

(50) -> [i for i in 1..]
(50) [1,2,3,4,5,6,7,8,9,10,...]
Type: Stream PositiveInteger
(51) -> [i for i in 1.. | prime?(i)]
(51) [2,3,5,7,11,13,17,19,23,29,...]
Type: Stream PositiveInteger

In each case the first few elements of the stream are calculated for
display purposes but the rest of the stream remains unevaluated. The value
of items in a stream are only calculated when they are needed which gives
rise to their alternative name of ``lazy lists''.

Another method of creating streams is to use the generate(f,a)
function. This applies it's first argument repeatedly onto it's second to
produce the stream [a,f(a),f(f(a)),f(f(f(a))) ...]?. Given
that the function nextPrime returns the lowest prime
number greater than it's argument we can generate a stream of primes
as follows:

As a longer example a stream of Fibonacci numbers will be computed. The
Fibobacci numbers start at 1 and each following number is the addition
of the two numbers that precede it i.e. 1,1,2,3,5,8,... The idea
behind this example is from
[jenks1992, page 457]
but the explanation and construction below is that of the author's.

Since the generation of any Fibonacci number only relies on knowing the
previous two numbers we can look at the series through a window of two
elements. To create the series the window is placed at the start over
the values [1,1]? and their sum obtained. The window is now
shifted to the right by one position and the sum placed into the empty
slot of the window; the process is then repeated. To implement this we
require a function that takes a list of two elements (the current view
of the window), adds them and outputs the new window. The result is the
function [a,b]? -> [b,a+b]?:

(53) -> win : List Integer -> List Integer
Type: Void
(54) -> win(x) == [x.2, x.1 + x.2]
Type: Void
(55) -> win([1,1])
(55) [1,2]
Type: List Integer
(56) -> win(%)
(56) [2,3]
Type: List Integer
(57) -> win(%)
(57) [3,5]
Type: List Integer

(For more details on functions definitions see
Section 6.3.)

Thus it can be seen that repeatedly applying win
to the results of the previous invocation, each element of the series
is obtained. Clearly win is an ideal function to
construct streams using the generate function:

One other function of interest is complete which
expands a finite stream derived from an infinite one (and thus
was still stored as an infinite stream) to form a finite stream.

One-dimensional arrays are homogenous (all the elements must have the
same type) and mutable like lists but unlike lists they are constant in
size and have uniform access time (it is just as quick to read the last
element of a one-dimensional array as it is to read the first: this is
not true for lists).

Since these arrays are mutable all the warnings that apply to lists
apply to arrays i.e. it is possible to modify an element in a copy
of an array and change the original:

(62) -> x := %
(62) [7,2,5,4,1,9]
Type: OneDimensionalArray PositiveInteger
(63) -> y := x
(63) [7,2,5,4,1,9]
Type: OneDimensionalArray PositiveInteger
(64) -> y.3 := 20; x
(64) [7,2,20,4,1,9]
Type: OneDimensionalArray PositiveInteger

Note that because these arrays are of fixed size the concat!
function cannot be applied to them without generating an error.
If arrays of this type are required use the FlexibleArray?
type constructor (see
Section 5.5).

One-dimensional arrays can be created using new
which specifies the size of the array and the initial value for each
of the elements. Other operations that can be applied to one-dimensional
arrays are map! which applies a mapping onto each
element, swap! which swaps two elements and
copyInto!(a,b,c) which copies the array b onto a
starting at position c:

Flexible arrays are designed to provide the efficiency of one-dimensional
arrays while retaining the flexibility of lists. They are implemented by
allocating a fixed block of storage for the array. If the array needs to
be expanded then a larger block of storage is allocated and the contents
of the old block are copied into the new one.

There are several operations that can be applied to this type, most
of which modify the array in situ. As a result these functions
all have names ending in ``!''. All the functions in the example
below are fairly obvious. The physicalLength returns
the actual length of the array as stored in memory while the
physicalLength! allows this value to be changed by the
user.

There are several things to point out concerning the previous examples.
Firstly although flexible arrays are mutable, making copies of these
arrays creates separate entities. This can be seen by the fact that the
modification of element b.2 above did not alter a. Secondly, the
merge! function can take an extra argument before the
two arrays being merged. The argument is a comparison function and
defaults to <= if omitted. Lastly, shrinkable
tells the AXIOM system whether or not to let flexible arrays
contract when elements are deleted from them. An explicit package
reference must be given as in the example above.

Although there are several other data structures that ought to
be covered here such as sets and records, I want to move on to
more important areas such as functions and iteration before finishing
this chapter off.

By now the reader should be able to construct simple one-line
expressions involving variables and different data structures. This
chapter builds on this knowledge and shows how to use iteration,
make choices and build functions in AXIOM. At the moment it is
assumed that the reader has a rough idea of how types are specified
and constructed so that they can follow the examples given. A more
detailed coverage of types in AXIOM will be provided in a later
chapter (sometime in the future!).

From this point on most examples will be taken from input files.
The prompt for such examples is faked and should actually contain
a command such as ``)read eg01.input''. There may be jumps
in the step numbers of the examples due to the way AXIOM
calculates them and due to the editing done on the output. If the
example code is free from bugs then a value and type for the code
will be given otherwise just the code will be displayed

Although the author has not covered this topic yet please make do
with the following snippet until I write that section:

The AXIOM constructs that provide looping, choices and user-defined
functions all rely on the notion of blocks. According to
[jenks1992, page 112]
a block is a sequence of expressions which are evaluated in the order that
they appear except when it is modified by control expressions such as loops.
To leave a block prematurely use the expression BoolExpr? => Expr
where BoolExpr? is any AXIOM expression that has type
Boolean. The value and type of Expr determines the value and
type returned by the block.

If blocks are entered at the keyboard (as opposed to reading code
in from a text file) then there is only one way of creating them;
the syntax is:

(expression1;expression2; ...
;expressionN)

In an input file a block can be constructed as above or by placing
all the statements at the same indentation level. In this situation
the block is called a pile. As an example of a simple block a
list of three integers can be constructed using parentheses

(1) -> (a := 4;b := 1; c := 9; L := [a,b,c])
(1) [4,1,9]
Type: List PositiveInteger

or using piles

(2) -> L :=
a := 4
b := 1
c := 9
[a,b,c]
(2) [4,1,9]
Type: List PositiveInteger

Since blocks have a type and a value they can be used as arguments to
functions or as part of other expressions. It should be pointed out
that the following example is not recommended practice but helps to
illustrate the idea of blocks and their ability to return values:

(3) -> sqrt(4.0 +
a := 3.0
b := 1.0
c := a + b
c
)
(3) 2.8284271247 461900976
Type: Float

Note that indentation is extremely important. If the example
given above had the pile starting at ``a :='' moved left by
two spaces so that the ``a'' was under the ``('' of the
first line then the interpreter would signal an error. Furthermore,
if the closing bracket ``)'' is moved up to give:

(4) -> sqrt(4.0 +
a := 3.0
b := 1.0
c := a + b
c)

then the parser will generate errors and if the bracket is
shifted right by several spaces so that it is in line with the ``c''
similar errors will be raised. Finally, the ``)'' must be
indented by at least one space otherwise more errors will be generated.

Thus it can be seen that great care needs to be taken when
constructing input files consisting of piles of expressions. It would
seem prudent to add one pile at a time and check it is acceptable
before adding more, particularly if piles are nested. However, it
should be pointed out that the use of piles as values for functions
is not very readable and so perhaps the delicate nature of their
interpretation should deter programmers from using them in these
situations. Using piles should really be restricted to constructing
functions etc. and a small amount of rewriting can remove the
need to use them as arguments. For example, the previous block could
easily be implemented as:

(4) -> a := 3.0
b := 1.0
c := a + b
sqrt(4.0 + c)
(4) 2.8284271247 461900976
Type: Float

which achieves the same result and is easier to understand. Note
that this is still a pile but it is not as fragile as the previous
version.

Definitions of functions in AXIOM are quite simple providing two
things are observed. Firstly the type of the function must either be
completely specifed or completely unspecified and secondly the body
of the function is assigned to the function identifier using the
delayed assignment operator ``==''.

To specify the type of something the ``:'' operator is
used. Thus to define a variable x to be of type
Fraction Integer we enter:

(5) -> x : Fraction Integer
Type: Void

For functions the method is the same except that the arguments to
are placed in parentheses and the return type is placed after the
symbol ``->''. More details on type definitions will be
given later which will explain this more fully. For now an example
of type definitions for functions taking zero, one, two and three
integer arguments and returning a list of integers are given:

(6) -> f : () -> List Integer
Type: Void
(7) -> g : (Integer) -> List Integer
Type: Void
(8) -> h : (Integer, Integer) -> List Integer
Type: Void
(9) -> k : (Integer, Integer, Integer) -> List Integer
Type: Void

Now the actual definition of the functions might be:

(14) -> f()
(14) []
Type: List Integer
(15) -> g(4)
(15) [4]
Type: List Integer
(16) -> h(2,9)
(16) [2,9]
Type: List Integer
(17) -> k(-3,42,100)
(17) [-3,42,100]
Type: List Integer

The value returned by a function is either the value of the last
expression evaluated or the result of a return
statement. For example the following are effectively the same:

(18) -> p : Integer -> Integer
p x ==
a := 1
b := 2
a + b + x
Type: Void
(19) -> p : Integer -> Integer
p x ==
a := 1
b := 2
return a + b + x
Type: Void

Note that a block (pile) is assigned to the function identifier
p and thus all the rules about blocks apply to
function definitions. Also there was only one argument so the
parentheses were discarded.

This is basically all that one needs to know about defining functions
in AXIOM - first specify the complete type and then assign a block
to the function. The rest of this chapter is concerned with defining
more complex blocks than those in this section and as a result function
definitions will crop up continually particularly since they are a
good way of testing examples.

Apart from the ``=>'' operator that allows a block to be
left before the end AXIOM provides the standard if-then-else
construct. The general syntax is:

where ``else Expr2'' can be omitted. If the
expression BooleanExpr? evaluates to true then Expr1 is
executed otherwise Expr2 (if present) will be. An example of
piles and if-then-else in AXIOM from
[jenks1992, page 114]
is given below:

(20) -> h := 2.0
if h > 3.1 then
1.0
else
z := cos(h)
max(z,0.5)
(20) 0.5
Type: Float

Note the indentation - the ``else'' must be indented relative to
the ``if'' otherwise it will generate an error (AXIOM will think
there are two piles, the second one beginning with ``else'').

Any expression that has type Boolean can be used as
BooleanExpr? and the most common will be those involving the relational
operators ``>'', ``<'' and ``=''. Usually
the type of an expression involving the equality operator ``='' will
be Boolean but in those situations when it isn't you may need to use
the ``@'' operator to ensure that it is.

Loops in AXIOM are regarded as expressions containing another expression
called the loop body. The loop body is executed zero or more
times depending on the type of the loop. Loops can be nested to any depth.

The simplest type of loop provided by AXIOM is the repeat
loop. The general syntax of this is:

repeat loopBody

This will cause AXIOM to execute loopBody repeatedly until
either a break or
return statement are encountered. If loopBody
contains neither of these statements then it will loop forever. The
following piece of code will display the numbers from 1 to 4:

(21) -> i := 1
repeat
if i > 4 then break
output(i)
i := i + 1
1
2
3
4
Type: Void

Note: In AXIOM Release 2.0 the
break keyword replaces leave which was
used in earlier versions. Use of leave will generate an
error if it is used with AXIOM Release 2.0 or higher.

It was mentioned that loops will only be left when either a
break or return statement is
encountered so why can't one use the ``=>'' operator?
The reason is that the ``=>'' operator tells AXIOM
to leave the current block whereas break and
return tell AXIOM to leave the current loop
(the latter actually tells AXIOM to leave the current function
which could mean leaving several levels of loop if necessary).
An example of this is given in
[jenks1992]
and is provided here for reference:

(22) -> i := 0
repeat
i := i + 1
i > 3 => i
output(i)
1
2
3
^C
>> Sytem error:
Console interrupt.

Here the ``=>'' operator instructs AXIOM to leave
the block when the value of i reaches 4 but then the loop continues
to execute the first statement (increment i) forever (or until the
user interrupts it). However, the break keyword can be used to
represent a return value so we can combine the two previous examples as
follows:

(22) -> i := 0
repeat
i := i + 1
i > 3 => break
output(i)
1
2
3
Type: Void

To skip the rest of a loop body and begin the next iteration of
the loop one uses the iterate keyword:

(23) -> i := 0
repeat
i := i + 1
if i > 6 then break
-- Return to start if i is odd.
if odd?(i) then iterate
output(i)
2
4
6
Type: Void

The while statement extends the basic
repeat loop to place the control of leaving the
loop at the start rather than have it buried in the middle. Since
the body of the loop is still part of a repeat loop,
break and ``=>'' work in the same way as in the
previous section. The general syntax of a while loop is:

As before, BoolExpr? must be an expression of type Boolean.
Before the body of the loop is executed BoolExpr? is tested. If it
evaluates to true then the loop body is entered otherwise the loop
is terminated. Multiple conditions can be applied using the logical
operators such as and or by using several
while statements before the repeat.
The following examples are from
[jenks1992, page 122]:

(24) -> x := 1; y := 1
while x < 4 and y < 10 repeat
output [x,y]
x := x + 1
y := y + 2
[1,1]
[2,3]
[3,5]
Type: Void
(25) -> x := 1; y := 1
while x < 4 while y < 10 repeat
output [x,y]
x := x + 1
y := y + 2
[1,1]
[2,3]
[3,5]
Type: Void

Note that the last example using two while statements
is not a nested loop but the following one is:

(26) -> x := 1; y := 1
while x < 4 repeat
while y < 10 repeat
output [x,y]
x := x + 1
y := y + 2
[1,1]
[2,3]
[3,5]
[4,7]
[5,9]
Type: Void

As a longer example the following is taken from
[jenks1992, page 123]
although the matrix used is different. Given a matrix of arbitrary size, find
the position and value of the first negative element by examining the
matrix in row-major order:

(27) -> m := matrix [[21,37,53,14],_
[8,22,-24,16],_
[2,10,15,14],_
[26,33,55,-13]]
+21 37 53 14 +
| |
|8 22 - 24 16 |
(27) | |
|2 10 15 14 |
| |
+26 33 55 - 13+
Type: Matrix Integer
(28) -> lastrow := nrows(m); lastcol := ncols(m)
r := 1
while r <= lastrow repeat
c := 1 -- Index of first column
while c <= lastcol repeat
if elt(m,r,c) < 0 then
output [r,c,elt(m,r,c)]
r := lastrow
break -- Don't look any further
c := c + 1
r := r + 1
[2,3,-24]
Type: Void

The last loop of interest is the for loop. There are
two ways of creating a for loop and the general syntax
for both is as follows (with and without the ``such that'' predicates):

for var in seg repeat loopBody for var in list repeat loopBody for var in seg | BoolExpr?
repeat loopBody for var in list | BoolExpr?
repeat loopBody

where var is an index variable which is iterated over
the values in seg or list. The value seg is
a segment e.g. 1..10 or 1.. and list is
a list of some type. The last two types of for loop will
only execute the statements in loopBody if BoolExpr?
evaluates to true. Some examples of this type of loop are
given below:

(29) -> for i in 1..10 repeat
~prime?(i) => iterate
output(i)
2
3
5
7
Type: Void

(30) -> for i in 1..10 | prime?(i) repeat
output(i)
2
3
5
7
Type: Void

(31) -> for w in ["This", "Is", "Your", "Life!"] repeat
output(w)
This
Is
Your
Life
Type: Void

(32) -> for i in 1.. repeat
if even?(i) then output(i)
if i < 7 then iterate
break
2
4
6
Type: Void

The last example loop can be simplified by adding a
while clause:

(33) -> for i in 1.. while i < 7 repeat
if even?(i) then output(i)
2
4
6
Type: Void

and by using the ``such that'' clause it becomes even
simpler:

(34) -> for i in 1.. | even?(i) while i < 7 repeat
output(i)
2
4
6
Type: Void

Similarly it is possible to have multiple for
clauses on a line to iterate over several symbols in parallel:

(35) -> for a in 1..4 for b in 5..8 repeat
output [a,b]
[1,5]
[2,6]
[3,7]
[4,8]
Type: Void

As a general point it should be noted that any symbols referred
to in the ``such that'' and while clauses must be
pre-defined. This either means that the symbols must have been
defined in an outer level (e.g. in an enclosing loop) or in a
for clause appearing before the ``such that'' or
while. For example,

(36) -> for a in 1..4 repeat
for j in 7..9 | prime?(a + b) repeat
output [a,b,a + b]
[2,9,11]
[3,8,11]
[4,7,11]
[4,9,13]
Type: Void

is legal but

(37) -> for b in 7..9 | prime?(a + b) repeat
for a in 1..4 repeat
output [a,b,a + b]

is not and will generate an error because is not defined
in the first line.

Finally it is often useful to be able to use a segment in which the
elements are in decreasing order. To do this the by
keyword is used:

(38) -> for a in 1..4 for b in 8..5 by -1 repeat
output [a,b]
[1,8]
[2,7]
[3,6]
[4,5]
Type: Void

Note that without ``by -1'' the loop body would never
be executed (the segment 8..5 is empty so there is nothing to
iterate over):

(39) -> for a in 1..4 for b in 8..5 by -1 repeat
output [a,b]
Type: Void

Stephen M. Watt, Peter A. Broadbery, Samuel S. Dooley, Pietro Iglio,
Scott C. Morrison, Jonathon M. Steinbach, and Robert S. Sutor.
AXIOM Library Compiler User Guide.
NAG Ltd., first edition, March 1995. Reprinted with
corrections from November 1994.

Martin Dunstan (mnd@dcs.st-andrews.ac.uk)
Updated: 2nd May, 1996.