0.7 Functions, Choices, and Loops

By now the reader should be able to construct simple one-line expressions involving variables and different data structures. This section 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.

From this point on most examples will be taken from input files.

0.7.1 Reading Code from a File

Input files contain code that will be fed to the command prompt. The primary different between the command line and an input file is that indentation matters. In an input file you can specify piles of code by using indentation.

The names of all input files in FriCAS should end in .input otherwise FriCAS will refuse to read them.

If an input file is named foo.input you can feed the contents of the file to the command prompt (as though you typed them) by writing: )read foo.input.

It is good practice to start each input file with the )clear all command so that all functions and variables in the current environment are erased.

0.7.2 Blocks

The FriCAS constructs that provide looping, choices, and user-defined functions all rely on the notion of blocks. 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 an expression of the form: 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 them 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. When indentation is used to indicate program structure the block is called a pile. As an example of a simple block a list of three integers can be constructed using parentheses:

( a:=4; b:=1; c:=9; L:=[a,b,c])
\[[4,1,9]\]

Type: List PositiveInteger

Doing the same thing using piles in an input file you could type:

L :=
  a:=4
  b:=1
  c:=9
  [a,b,c]
\[[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:

sqrt(4.0 +
         a:=3.0
         b:=1.0
         c:=a + b
         c
    )
\[2.8284271247461900976\]

Type: Float

Note that indentation is extremely important. If the example 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 parenthesis ) is moved up to give

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

Error

Line   1: sqrt(4.0 +          ....A Error  A: Missing mate. Line   2:          a:=3.0 Line   3:          b:=1.0 Line   4:          c:=a + b Line   5:          c)          .........AB Error  A: (from A up to B) Ignored. Error  B: Improper syntax. Error  B: syntax error at top level Error  B: Possibly missing a )  5 error(s) parsing

then the parser will generate errors. If the parenthesis is shifted right by several spaces so that it is in line with the c thus:

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

Error

Line   1: sqrt(4.0 +          ....A Error  A: Missing mate. Line   2:          a:=3.0 Line   3:          b:=1.0 Line   4:          c:=a + b Line   5:          c Line   6:          )          .........A Error  A: (from A up to A) Ignored. Error  A: Improper syntax. Error  A: syntax error at top level Error  A: Possibly missing a )  5 error(s) parsing

a similar error will be raised. Finally, the ) must be indented by at least one space relative to the sqrt thus:

sqrt(4.0 +
         a:=3.0
         b:=1.0
         c:=a + b
         c
 )
\[2.8284271247461900976\]

Type: Float

or an error will be generated.

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

a:=3.0
b:=1.0
c:=a + b
sqrt(4.0 + c)
a:=3.0
\[3.0\]

Type: Float

b:=1.0
\[1.0\]

Type: Float

c:=a + b
\[4.0\]

Type: Float

sqrt(4.0 + c)
\[2.8284271247461900976\]

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.

0.7.3 Functions

Definitions of functions in FriCAS are quite simple providing two things are observed. First, the type of the function must either be completely specified or completely unspecified. Second, 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:

x : Fraction Integer

Type: Void

For functions the method is the same except that the arguments are placed in parentheses and the return type is placed after the symbol ->. Some examples of function definitions taking zero, one, two, or three arguments and returning a list of integers are:

f : () -> List Integer

Type: Void

g : (Integer) -> List Integer

Type: Void

h : (Integer, Integer) -> List Integer

Type: Void

k : (Integer, Integer, Integer) -> List Integer

Type: Void

Now the actual function definitions might be:

f() == [ ]

Type: Void

g(a) == [a]

Type: Void

h(a,b) == [a,b]

Type: Void

k(a,b,c) == [a,b,c]

Type: Void

with some invocations of these functions:

f()
Compiling function f with type () -> List Integer
\[[]\]

Type: List Integer

g(4)
Compiling function g with type Integer -> List Integer
\[[4]\]

Type: List Integer

h(2,9)
Compiling function h with type (Integer,Integer) -> List Integer
\[[2,9]\]

Type: List Integer

k(-3,42,100)
Compiling function k with type (Integer,Integer,Integer) -> List
   Integer
\[[-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:

p : Integer -> Integer

Type: Void

p x == (a:=1; b:=2; a+b+x)

Type: Void

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 parenthese are not needed.

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 name. The rest of this section 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. Since the block structure is more complex we will use the pile notation and thus have to use input files to read the piles.

0.7.4 Choices

Apart from the => operator that allows a block to exit 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 executed. An example of piles and if-then-else is: (read from an input file)

h := 2.0
if h > 3.1 then
      1.0
   else
      z:= cos(h)
      max(x,0.5)
h := 2.0
\[2.0\]

Type: Float

if h > 3.1 then
      1.0
   else
      z:= cos(h)
      max(x,0.5)
\[x\]

Type: Polynomial 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.

0.7.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 kind of loop. Loops can be nested to any depth.

0.7.5.1 The repeat loop

The simplest kind 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 is 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:

i:=1
repeat
  if i > 4 then break
  output(i)
  i:=i+1
i:=1
\[1\]

Type: PositiveInteger

repeat
  if i > 4 then break
  output(i)
  i:=i+1

   1
   2
   3
   4

Type: Void

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 leaves the current loop. The return statement leave the current function.

To skip the rest of a loop body and continue the next iteration of the loop use the iterate statement (the – starts a comment in FriCAS)

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)
i := 0
\[0\]

Type: NonNegativeInteger

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

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

x:=1
y:=1
while x < 4 and y < 10 repeat
  output [x,y]
  x := x + 1
  y := y + 2
x:=1
\[1\]

Type: PositiveInteger

y:=1
\[1\]

Type: PositiveInteger

while x < 4 and y < 10 repeat
  output [x,y]
  x := x + 1
  y := y + 2

   [1,1]
   [2,3]
   [3,5]

Type: Void

x:=1
y:=1
while x < 4 while y < 10 repeat
  output [x,y]
  x := x + 1
  y := y + 2
x:=1
\[1\]

Type: PositiveInteger

y:=1
\[1\]

Type: PositiveInteger

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:

x:=1
y:=1
while x < 4 repeat
  while y < 10 repeat
    output [x,y]
    x := x + 1
    y := y + 2
x:=1
\[1\]

Type: PositiveInteger

y:=1
\[1\]

Type: PositiveInteger

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

Suppose we that, given a matrix of arbitrary size, find the position and value of the first negative element by examining the matrix in row-major order:

m := matrix [ [ 21, 37, 53, 14 ],_
              [  8, 22,-24, 16 ],_
              [  2, 10, 15, 14 ],_
              [ 26, 33, 55,-13 ] ]
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
m := matrix [ [ 21, 37, 53, 14 ],_
              [  8, 22,-24, 16 ],_
              [  2, 10, 15, 14 ],_
              [ 26, 33, 55,-13 ] ]

[21375314822-24162101514263355-13]

Type: Matrix Integer

lastrow := nrows(m)
\[4\]

Type: PositiveInteger

lastcol := ncols(m)
\[4\]

Type: PositiveInteger

r := 1
\[1\]

Type: PositiveInteger

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

0.7.5.3 The for loop

The last loop statement of interest is the for loop. There are two ways of creating a for loop. The first way uses either a list or a segment:

for var in seg repeat loopBody
for var in list repeat loopBody

where var is an index variable which is iterated over the values in seg or list. The value seg is a segment such as 1…10 or 1… and list is a list of some type. For example:

for i in 1..10 repeat
  ~prime?(i) => iterate
  output(i)

   2
   3
   5
   7

Type: Void

for w in ["This", "is", "your", "life!"] repeat
  output(w)
for w in ["This", "is", "your", "life!"] repeat
  output(w)

   This
   is
   your
   life!

Type: Void

The second form of the for loop syntax includes a `` such that`` clause which must be of type Boolean:

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

Some examples are:

for i in 1..10 | prime?(i) repeat
  output(i)

   2
   3
   5
   7

Type: Void

for i in [1,2,3,4,5,6,7,8,9,10] | prime?(i) repeat
  output(i)

   2
   3
   5
   7

Type: Void

You can also use a while clause:

for i in 1.. while i < 7 repeat
  if even?(i) then output(i)

   2
   4
   6

Type: Void

Using the `` such that`` clause makes this appear simpler:

for i in 1.. | even?(i) while i < 7 repeat
  output(i)

   2
   4
   6

Type: Void

You can use multiple for clauses to iterate over several sequences in parallel:

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:

for a in 1..4 repeat
  for b 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

Finally, the for statement has a by clause to specify the step size. This makes it possible to iterate over the segment in reverse order:

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 the by -1 the segment 8..5 is empty so there is nothing to iterate over and the loop exits immediately.