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])
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]
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
)
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
)
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
Type: Float
b:=1.0
Type: Float
c:=a + b
Type: Float
sqrt(4.0 + c)
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
Type: List Integer
h(2,9)
Compiling function h with type (Integer,Integer) -> List Integer
Type: List Integer
k(-3,42,100)
Compiling function k with type (Integer,Integer,Integer) -> List
Integer
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
Type: Float
if h > 3.1 then
1.0
else
z:= cos(h)
max(x,0.5)
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
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
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
Type: PositiveInteger
y:=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
Type: PositiveInteger
y:=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
Type: PositiveInteger
y:=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)
Type: PositiveInteger
lastcol := ncols(m)
Type: PositiveInteger
r := 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.