login  home  contents  what's new  discussion  bug reports     help  links  subscribe  changes  refresh  edit
A Tutorial Introduction To <a href="https://wiki.fricas.org/FriCAS">FriCAS</a>

An Introduction To Programming In

FriCAS


By Martin N. Dunstan

May 2, 1996

Contents

  1. Introduction
  2. Using FriCAS As A Pocket Calculator
    1. Basic Arithmetic
    2. Type Conversion
    3. Useful Functions
  3. Using FriCAS As A Symbolic Calculator
    1. Expressions Involving Symbols
    2. Complex Numbers
    3. Number Representations
    4. Modular Arithmetic
  4. General Points About FriCAS
    1. Computation Without Output
    2. Accessing Earlier Result
    3. Splitting Expressions Over Several Lines
    4. Comments And Descriptions
    5. Control Of Result Types
  5. Data Structures In FriCAS
    1. Lists
    2. Segmented Lists
    3. Streams
    4. Arrays, Vectors, Strings and Bits
    5. Flexible Arrays
  6. Functions, Choices And Loops
    1. Reading Code From A File
    2. Blocks
    3. Functions
    4. Choices
    5. Loops
      1. The repeat Loop
      2. The while Loop
      3. The for Loop

List Of Figures

2.1
Table of simple FriCAS functions.
2.2
Table of simple FriCAS operators.
2.3
Table of some useful FriCAS macros.


      
5.1
List u before setrest(endOfu,partOfu).
5.2
List u after setrest(endOfu,partOfu).

Chapter 1

Introduction

This document is intended to be a tutorial introduction to the FriCAS (Release 1.3.5) 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 "FriCAS Book":http://fricas.github.io/book.pdf (updated version of [jenks1992]) is an extremely useful book for those wanting to use the FriCAS system it is not quite so helpful for learning to program in FriCAS. 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 FriCAS requires a text that covers a significant part of the available features while keeping the number of pages to a minimum as FriCAS Book does. This is not to say that FriCAS Book does not explain how to program in FriCAS since this is how the author learned!

As a result the author has decided to produce this text as an introduction to the FriCAS 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 FriCAS but is by no means essential. FriCAS 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 FriCAS (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 FriCAS (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 FriCAS - the code for each example was copied from the LaTeX version of this document into the FriCAS 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.

Chapter 2

Using FriCAS As A Pocket Calculator

At the simplest level FriCAS 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.).

2.1 Basic Arithmetic

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 FriCAS 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:


      (2) -> 6.93 * 4.1328
      (2)    28.640304
                                                           Type: Float

      (3) -> 6.93 / 4.1328
      (3)    1.6768292682 926829268
                                                           Type: Float

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 but FriCAS will not do so automatically.

2.2 Type Conversion

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 ``/''):


      (6) -> (4/6) :: Float
      (6)    0.6666666666 6666666667
                                                           Type: Float

Although FriCAS 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:


      (7) -> % :: Fraction Integer

             2
      (7)    -
             3
                                                Type: Fraction Integer

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

Although FriCAS 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 FriCAS 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 FriCAS to do all the necessary symbolic manipulation and only at the end should actual numerical results be extracted.

If you bear in mind that FriCAS 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 FriCAS 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 (FriCAS obtains the fractional part of ``x'' using ``x - truncate x''):


      (8) -> round(3.77623)
      (8)    4.0
                                                           Type: Float

      (9) -> round(-3.77623)
      (9)    -4.0
                                                           Type: Float

     (10) -> truncate(9.235)
     (10)    9.0
                                                           Type: Float

     (11) -> truncate(-9.654)
     (11)    -9.0
                                                           Type: Float

     (12) -> fractionPart(-3.77623)
     (12)    -0.77623
                                                           Type: Float

2.3 Useful Functions

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:


     (13) -> abs(4)
     (13)    4
                                                 Type: PositiveInteger

     (14) -> abs(-3)
     (14)    3
                                                 Type: PositiveInteger

     (15) -> abs(-34254.12314)
     (15)    34254.12314
                                                           Type: Float

     (16) -> sign(-49543.2345346)
     (16)    -1
                                                         Type: Integer

     (17) -> sign(0)
     (17)    0
                                              Type: NonNegativeInteger

     (18) -> sign(234235.42354)
     (18)    1
                                                 Type: PositiveInteger

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:


     (19) -> positive?(-234)
     (19)    false
                                                         Type: Boolean

     (20) -> negative?(-234)
     (20)    true
                                                         Type: Boolean

     (21) -> zero?(42)
     (21)    false
                                                         Type: Boolean

     (22) -> one?(1)
     (22)    true
                                                         Type: Boolean

     (23) -> odd?(23)
     (23)    true
                                                         Type: Boolean

     (24) -> odd?(9.435)
     (24)    false
                                                         Type: Boolean

     (25) -> even?(-42)
     (25)    true
                                                         Type: Boolean

     (26) -> prime?(37)
     (26)    true
                                                         Type: Boolean

     (27) -> prime?(-37)
     (27)    false
                                                         Type: Boolean

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.


     +---------------+------------------------------------+
     | Function      | Operation                          |
     +---------------+------------------------------------+
     | sin(x)        | Sine of x                          |
     | cos(x)        | Cosine of x                        |
     | tan(x)        | Tangent of x                       |
     | asin(x)       | Arcsin of x                        |
     | acos(x)       | Arccos of x                        |
     | atan(x)       | Arctangent of x                    |
     | gcd(x,y)      | Greatest common divisor of x and y |
     | lcm(x,y)      | Lowest common multiple of x and y  |
     | max(x,y)      | Maximum of x and y                 |
     | min(x,y)      | Minimum of x and y                 |
     | factorial(x)  | Factorial of x                     |
     | factor(x)     | Prime factors of x                 |
     | divide(x,y)   | Quotient and remainder of x/y      |
     +---------------+------------------------------------+

            Figure 2.1: Table of simple FriCAS functions.


     +--------+--------------------+--------+------------------+
     | Symbol | Operation          | Symbol | Operation        |
     +--------+--------------------+--------+------------------+
     |  +     | Addition           |  -     | Subtraction      |
     |  -     | Numerical Negation |  ~     | Logical Negation |
     |  /\    | Conjunction (AND)  |  \/    | Disjunction (OR) |
     |  and   | Logical AND (/\)   |  or    | Logical OR (\/)  |
     |  not   | Logical Negation   |  ^     | Exponentiation   |
     |  *     | Multiplication     |  /     | Division         |
     |  quo   | Quotient           |  rem   | Remainder        |
     |  <     | Less than          |  >     | Greater than     |
     |  <=    | Less or equal      |  >=    | Greater or equal |
     +--------+--------------------+--------+------------------+

            Figure 2.2: Table of simple FriCAS operators.


     +----------------+------------------------------------+
     | Macro          | Value                              |
     +----------------+------------------------------------+
     | %i             | The square root of -1.             |
     | %e             | The base of the natural logarithm. |
     | %pi            | Pi.                                |
     | %infinity      | Infinity.                          |
     | %plusInfinity  | Positive Infinity.                 |
     | %minusInfinity | Negative Infinity.                 |
     +----------------+------------------------------------+

           Figure 2.3: Table of some useful FriCAS macros.


Chapter 3

Using FriCAS As A Symbolic Calculator

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.

3.1 Expressions Involving Symbols

Expressions involving symbols are entered just as they are written down, for example:

      (1) -> xSquared := x^2

              2
      (1)    x
                                              Type: Polynomial Integer

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:

      (2) -> xDummy := 3.21*x^2

                   2
      (2)    3.21 x
                                                Type: Polynomial Float

      (3) -> xDummy := x^2.5

              2 +-+
      (3)    x \|x
                                                Type: Expression Float

      (4) -> xDummy := x^3.3

              3 10+-+3
      (4)    x   \|x
                                                Type: Expression Float

      (5) -> xyDummy := x^2 - y^2

               2   2
      (5)    -y + x
                                              Type: Polynomial Integer

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:

      (6) -> eval(xDummy, x=3)
      (6)    37.5405075985 29552193
                                                Type: Expression Float

      (7) -> eval(xyDummy, [x=3, y=2.1])
      (7)    4.59
                                                Type: Polynomial Float

3.2 Complex Numbers

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 FriCAS which uses the %i macro to represent i, the square root of -1. Thus expressions involving complex numbers are entered just like other expressions:

      (8) -> (2/3 + %i)^3

               46   1
      (8)    - -- + - %i
               27   3
                                        Type: Complex Fraction Integer

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:

      (9) -> real(3 + 2*%i)
      (9)    3
                                                 Type: PositiveInteger

     (10) -> imag(3 + 2*%i)
     (10)    2
                                                 Type: PositiveInteger

     (11) -> conjugate(3 + 2*%i)
     (11)    3 - 2%i
                                                 Type: Complex Integer

The function factor can also be applied to complex numbers but the results aren't quite so obvious as for factoring integers:

     (12) -> factor(144 + 24*%i)

                        6
     (12)    %i (1 + %i)  3 (6 + %i)
                                        Type: Factored Complex Integer

3.3 Number Representations

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:

     (13) -> digits(40)
     (13)    20
                                                 Type: PositiveInteger

     (14) -> %pi :: Float
     (14)    3.1415926535 8979323846 2643383279 502884197
                                                           Type: Float

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 FriCAS to display floating-point values in exponent notation and the latter causes it to use fixed point notation. For example:

     (15) -> outputFloating(); %
     (15)    0.3141592653 5897932384 6264338327 9502884197 E 1
                                                           Type: Float

     (16) -> outputFloating(3); 0.00345
     (16)    0.345 E -2
                                                           Type: Float

     (17) -> outputFixed(); %
     (17)    0.00345
                                                           Type: Float

     (18) -> outputFixed(3); %
     (18)    0.003
                                                           Type: Float

     (19) -> outputGeneral(); %
     (19)    0.00345
                                                           Type: Float

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:

     (20) -> radix(10^10,32)
     (20)    9A0NP00
                                               Type: RadixExpansion 32

     (21) -> radix(3/21,5)
               ______
     (21)    0.032412
                                                Type: RadixExpansion 5

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.

     (22) -> decimal(22/7)
               ______
     (22)    3.142857
                                                Type: DecimalExpansion

     (23) -> continuedFraction(6543/210)

                    1 |     1 |     1 |     1 |
     (23)    31 + +---+ + +---+ + +---+ + +---+
                  | 6     | 2     | 1     | 3
                                       Type: ContinuedFraction Integer

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:

     (24) -> partialFraction(234,40)

                 3    3
     (24)    6 - -- + -
                  2   5
                 2
                                         Type: PartialFraction Integer

     (25) -> padicFraction(%)

                 1   1    3
     (25)    6 - - - -- + -
                 2    2   5
                     2
                                         Type: PartialFraction Integer

     (26) -> compactFraction(%)

                 3    3
     (26)    6 - -- + -
                  2   5
                 2
                                         Type: PartialFraction Integer

     (27) -> padicFraction(234/40)

             117
     (27)    ---
             20
                                Type: PartialFraction Fraction Integer

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:

     (28) -> t := partialFraction(234,40)

                 3    3
     (28)    6 - -- + -
                  2   5
                 2
                                         Type: PartialFraction Integer

     (29) -> wholePart(t)
     (29)    6
                                                 Type: PositiveInteger

     (30) -> numberOfFractionalTerms(t)
     (30)    2
                                                 Type: PositiveInteger

     (31) -> p := nthFractionalTerm(t,1)

               3
     (31)    - --
                2
               2
                                         Type: PartialFraction Integer

     (32) -> firstNumer(p)
     (32)    -3
                                                         Type: Integer

     (33) -> firstDenom(p)

              2
     (33)    2
                                                Type: Factored Integer

3.4 Modular Arithmetic

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:

     (34) -> x : PrimeField 7 := 5
     (34)    5
                                                    Type: PrimeField 7

     (35) -> x^5 + 6
     (35)    2
                                                    Type: PrimeField 7

     (36) -> 1/x
     (36)    3
                                                    Type: PrimeField 7

The first example should be read as

``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:

     (37) -> y : IntegerMod 8 := 11
     (37)    3
                                                    Type: IntegerMod 8

     (38) -> y*4 + 27
     (38)    7
                                                    Type: IntegerMod 8

Note that polynomials can be constructed in a similar way:

     (39) -> (3*a^4 + 27*a - 36)::Polynomial PrimeField 7

               4
     (39)    3a  + 6a + 6
                                         Type: Polynomial PrimeField 7

Chapter 4

General Points About FriCAS

This chapter contains a jumble of various points about FriCAS.

4.1 Computation Without Output

It is sometimes desirable to enter an expression and prevent FriCAS 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):

      (1) -> 2 + 4*5;
                                                 Type: PositiveInteger

4.2 Accessing Earlier Results

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.

4.3 Splitting Expressions Over Several Lines

Although FriCAS 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:

      (3) -> ThisIsAVeryLong_
VariableName
      (3)    ThisIsAVeryLongVariableName
                            Type: Variable ThisIsAVeryLongVariableName

4.4 Comments And Descriptions

Comments and descriptions are really only of use in files of FriCAS 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 FriCAS 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 ``++''.

4.5 Control Of Result Types


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:

      (4) -> (2/3)$Float
      (4)    0.6666666666 6666666667
                                                           Type: Float

tells FriCAS 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 FriCAS 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.

Chapter 5

Data Structures in FriCAS

This chapter is an overview of some of the data structures provided by FriCAS.

5.1 Lists

The FriCAS List 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 FriCAS 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:

      (6) -> first([1,5,6,2,3]
      (6)    1
                                                 Type: PositiveInteger

      (7) -> first([1,5,6,2,3],2)
      (7)    [1,5]
                                            Type: List PositiveInteger

      (8) -> rest([1,5,6,2,3])
      (8)    [5,6,2,3]
                                            Type: List PositiveInteger

      (9) -> rest([1,5,6,2,3],2)
      (9)    [6,2,3]
                                            Type: List PositiveInteger

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.

     (10) -> empty?([7,2,-1,2])
     (10)    false
                                                         Type: Boolean

     (11) -> member?(-1,[7,2,-1,2])
     (11)    true
                                                         Type: Boolean

     (12) -> reverse([7,2,-1,2])
     (12)    [2,- 1,2,7]
                                                    Type: List Integer

     (13) -> sort([7,2,-1,2])
     (13)    [-1,2,2,7]
                                                    Type: List Integer

     (14) -> removeDuplicates([1,5,3,5,1,1,2])
     (14)    [1,5,3,2]
                                            Type: List PositiveInteger

     (15) -> #[7,2,-1,2]
     (15)    4
                                                 Type: PositiveInteger

Lists in FriCAS 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


[Image]

Figure 5.1: List u before setrest(endOfu,partOfu).



[Image]

Figure 5.2: List u after setrest(endOfu,partOfu).

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 FriCAS.

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

     (21) -> u.3
     (21)    4
                                                 Type: PositiveInteger

     (22) -> u.5
     (22)    1
                                                 Type: PositiveInteger

     (23) -> u.6
     (23)    4
                                                 Type: PositiveInteger

     (24) -> first rest rest u   -- Same as u.3
     (24)    4
                                                 Type: PositiveInteger

     (25) -> u.first
     (25)    9
                                                 Type: PositiveInteger

     (26) -> u(3)
     (26)    4
                                                 Type: PositiveInteger

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

Other list operations are:

     (29) -> L := [9,3,4,7]; #L
     (29)    4
                                                 Type: PositiveInteger

     (30) -> last(L)
     (30)    7
                                                 Type: PositiveInteger

     (31) -> L.last
     (31)    7
                                                 Type: PositiveInteger

     (32) -> L.(#L - 1)
     (32)    4
                                                 Type: PositiveInteger


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

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

5.2 Segmented Lists

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):

     (48) -> [1..]
     (48)    [1..]
                           Type: List UniversalSegment PositiveInteger

     (49) -> expand(%)
     (49)    [1,2,3,4,5,6,7,8,9,10,...]
                                                  Type: Stream Integer

5.3 Streams

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:

     (52) -> generate(nextPrime,2)$Stream Integer
     (52)    [2,3,5,7,11,13,17,19,23,29,...]
                                                  Type: Stream Integer

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:

     (58) -> fibs := [generate(win,[1,1])]
     (58)    [[1,1],[1,2],[2,3],[3,5],[5,8],[8,13],...]
                                             Type: Stream List Integer

This isn't quite what is wanted - we need to extract the first element of each list and place that in our series:

     (59) -> fibs := [i.1 for i in [generate(win,[1,1])]]
     (59)    [1,1,2,3,5,8,13,21,34,55,...]
                                                  Type: Stream Integer

Obtaining the 200th Fibonacci number is trivial:

     (60) -> fibs.200
     (60)    280571172992510140037611932413038677189525
                                                 Type: PositiveInteger

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.

5.4 Arrays, Vectors, Strings and Bits

The simplest array data structure is the one-dimensional array which can be obtained by applying the oneDimensionalArray function to a list:

     (61) -> oneDimensionalArray([7,2,5,4,1,9])
     (61)    [7,2,5,4,1,9]
                             Type: OneDimensionalArray PositiveInteger

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:

     (65) -> a : ARRAY1 PositiveInteger := new(10,3)
     (65)    [3,3,3,3,3,3,3,3,3,3]
                             Type: OneDimensionalArray PositiveInteger

     (66) -> map!(i +-> i+1,a); a
     (66)    [4,4,4,4,4,4,4,4,4,4]
                                     Type: OneDimensionalArray Integer

     (67) -> b := oneDimensionalArray([2,3,4,5,6])
     (67)    [2,3,4,5,6]
                             Type: OneDimensionalArray PositiveInteger

     (68) -> swap!(b,2,3); b
     (68)    [2,4,3,5,6]
                             Type: OneDimensionalArray PositiveInteger

     (69) -> copyInto!(a,b,3)
     (69)    [4,4,2,4,3,5,6,4,4,4]
                             Type: OneDimensionalArray PositiveInteger

     (70) -> a
     (70)    [4,4,2,4,3,5,6,4,4,4]
                             Type: OneDimensionalArray PositiveInteger

(note that ARRAY1 is an abbreviation for the type OneDimensionalArray?.) Other types based on one-dimensional arrays are Vector, String and Bits:

     (71) -> vector([1/2,1/3,1/4])
              1 1 1
     (71)    [-,-,-]
              2 3 4
                                         Type: Vector Fraction Integer

     (72) -> "Hello World"
     (72)    "Hello World"
                                                          Type: String

     (73) -> bits(8,true)
     (73)    "11111111"
                                                            Type: Bits

A vector is similar to a one-dimensional array except that if it's components belong to a ring then arithmetic operations are provided.

5.5 Flexible Arrays

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.

     (74) -> f : FARRAY INT := new(6,1)
     (74)    [1,1,1,1,1,1]
                                           Type: FlexibleArray Integer

     (75) -> f.1 := 4;f.2 := 3;f.3 := 8;f.5 := 2;f
     (75)    [4,3,8,1,2,1]
                                           Type: FlexibleArray Integer

     (76) -> insert!(42,f,3); f
     (76)    [4,3,42,8,1,2,1]
                                           Type: FlexibleArray Integer

     (77) -> insert!(28,f,8); f
     (77)    [4,3,42,8,1,2,1,28]
                                           Type: FlexibleArray Integer

     (78) -> removeDuplicates!(f)
     (78)    [4,3,42,8,1,2,28]
                                           Type: FlexibleArray Integer

     (79) -> delete!(f,5)
     (79)    [4,3,42,8,2,28]
                                           Type: FlexibleArray Integer

     (80) -> g := f(3..5)
     (80)    [42,8,2]
                                           Type: FlexibleArray Integer

     (81) -> g.2 := 7; f
     (81)    [4,3,42,8,2,28]
                                           Type: FlexibleArray Integer

     (82) -> insert!(g,f,1)
     (82)    [42,7,2,4,3,42,8,2,28]
                                           Type: FlexibleArray Integer

     (83) -> physicalLength(f)
     (83)    10
                                                 Type: PositiveInteger

     (84) -> physicalLength!(f,20)
     (84)    [42,7,2,4,3,42,8,2,28]
                                           Type: FlexibleArray Integer

     (85) -> merge!(sort!(f),sort!(g))
     (85)    [2,2,2,3,4,7,7,8,28,42,42,42]
                                           Type: FlexibleArray Integer

     (86) -> shrinkable(false)$FlexibleArray(Integer)
     (86)    true
                                                         Type: Boolean

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 FriCAS 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.

Chapter 6

Functions, Choices And Loops

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 FriCAS. 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 FriCAS 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 FriCAS 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:

6.1 Reading Code From A File

  • The names of all input files in FriCAS should end in the suffix ``.input'' otherwise FriCAS will refuse to consult them.
  • Use the command )read fred.input to read and interpret the contents of the file ``fred.input''.
  • To start everything afresh before reading in an input file enter the command )clear all or put it at the start of the file.

6.2 Blocks

The FriCAS 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 FriCAS 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.

6.3 Functions

Definitions of functions in FriCAS 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:

     (10) -> f() == []
                                                            Type: Void
     (11) -> g(a) == [a]
                                                            Type: Void
     (12) -> h(a,b) == [a,b]
                                                            Type: Void
     (13) -> k(a,b,c) == [a,b,c]
                                                            Type: Void

with some invokations of these functions:

     (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 FriCAS - 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.

6.4 Choices

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

if BooleanExpr? then Expr1 else Expr2

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 FriCAS 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 (FriCAS 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.

6.5 Loops

Loops in FriCAS 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.

6.5.1 The repeat Loop

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

repeat loopBody

This will cause FriCAS 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 FriCAS Release 1.3.5 the break keyword replaces leave which was used in old AXIOM versions. Use of leave will generate an error if it is used with FriCAS.

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 FriCAS to leave the current block whereas break and return tell FriCAS to leave the current loop (the latter actually tells FriCAS 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 FriCAS 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

6.5.2 The while Loop

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:

while BoolExpr? repeat loopBody

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

6.5.3 The for Loop

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

Bibliography

[jenks1992]?
Richard D. Jenks and Robert S. Sutor. AXIOM: the scientific computation system. NAG Ltd., 1992.
[watt1995]?
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.



  Subject:   Be Bold !!
  ( 15 subscribers )  
Please rate this page: