Functions in GAP
Overview
Teaching: 40 min
Exercises: 15 minQuestions
Functions as a way of code re-use
Objectives
Using command line for prototyping
Creating functions
Reading GAP code from a file
Just to remind our task: for a finite group G, we would like to calculate the average order of its elements (that is, the sum of orders of its elements divided by the order of the group).
We begin with a very straightforward approach, iterating over all elements of the group in question:
S:=SymmetricGroup(10);
Sym( [ 1 .. 10 ] )
sum:=0;
0
for g in S do
sum := sum + Order(g);
od;
sum/Size(S);
39020911/3628800
Now assume that we would like to save this fragment of the GAP code and later
repeat this calculation for some other groups. We may even reformat it to fit
it into one line and use double semicolon to suppress the output of sum
:
sum:=0;; for g in S do sum := sum + Order(g); od; sum/Size(S);
39020911/3628800
Now we may easily copy and paste it into the GAP session when we will need it next time.
But here we see the first inconvenience: the code expects that the group in question
must be stored in a variable named S
, so either we can have only with one group S
at a time, or we need to edit the code:
S:=AlternatingGroup(10);
Alt( [ 1 .. 10 ] )
sum:=0;; for g in S do sum := sum + Order(g); od; sum/Size(S);
2587393/259200
This works only for rapid prototyping
- incidentally, one could copy and paste only a part of the code, and incomplete input may trigger a break loop;
- even more dangerous: one could forget to reset
sum
to zero prior to the new calculation and obtain incorrect results;- the group in question may have a different variable name, so the code will have to be changed;
- last, but not least: when GAP code is pasted into the interpreter, it is evaluated line by line. If you have a long file with many commands, and the syntax error is in the line N, this error will be reported only when GAP will complete the evaluation of all preceding lines, and that might be quite time-consuming.
That is why we need to give the GAP code more structure by organising it into functions:
- functions are parsed first and may be called later;
- any syntax errors will be detected in the parsing stage, and not at the time of the call;
- functions may have local variables, and this prevents them being accidentally overwritten just because of reusing the same name of the variable to store something else.
The following function takes an argument S
and computes the average order
of its elements:
AvgOrdOfGroup := function(G)
local sum, g;
sum := 0;
for g in G do
sum := sum + Order(g);
od;
return sum/Size(G);
end;
function( S ) ... end
Now we can apply it to another group, passing the group as an argument:
A:=AlternatingGroup(10); AvgOrdOfGroup(A); time;
Alt( [ 1 .. 10 ] )
2587393/259200
837
The example above also demonstrates time
- this is the variable which stores
the CPU time in milliseconds spent by the last command.
Thus, now we may create new groups and reuse AvgOrdOfGroup
to calculate average
order of their elements in the same GAP session. Our next goal is to make it
reusable for the future calculations.
Using a text editor (for example, the one that you have used for the previous
Software Carpentry lessons), create a text file called avgord.g
containing
the code as below. Lines that start with #
are comments and are ignored for the evaluation of your code.
Always comment your code so you know what it is you did when you come
back to it weeks later!
#####################################################################
#
# AvgOrdOfGroup(G)
#
# Calculating the average order of an element of G, where G meant to
# be a group but in fact may be any collection of objects having
# multiplicative order
#
AvgOrdOfGroup := function(G)
local sum, g;
sum := 0;
for g in G do
sum := sum + Order(g);
od;
return sum/Size(G);
end;
Now start a new GAP session and create another group, for example MathieuGroup(11)
:
M11:=MathieuGroup(11);
Group([ (1,2,3,4,5,6,7,8,9,10,11), (3,7,11,8)(4,10,5,6) ])
Clearly, AvgOrdOfGroup
is not defined in this session, so an attempt to
call this function results in an error:
AvgOrdOfGroup(M11);
Error, Variable: 'AvgOrdOfGroup' must have a value
not in any function at line 2 of *stdin*
To be available, first it should be loaded using the function Read
. Below
we assume that the file is in the current directory, so no path is needed).
Read("avgord.g");
This loads the file into GAP, and the function AvgOrdOfGroup
is now
available:
AvgOrdOfGroup(M11);
53131/7920
In the example of using Read
, a new GAP session was started to make it clear
that AvgOrdOfGroup
did not exist before the call of Read
and was loaded
from the file. However, a file with a function like this could be read multiple
times in the same GAP session (later you will see cases when re-reading a
file is more complicated). Calling Read
again executes all code in the file
being read. This means that if the code of the function has been modified, and
it has no errors (but possibly has warnings), the function will be
overwritten.
For example, let us edit the file and replace the line
return sum/Size(G);
by the line with a deliberate syntax error:
return Float(sum/Size(G);
Now read this file with
Read("avgord.g");
and you will see an error message:
Syntax error: ) expected in avgord.g line 7
return Float(sum/Size(G);
^
In this case, the function remains the same:
Print(AvgOrdOfGroup);
function ( G )
for g in G do
sum := sum + Order( g );
od;
return sum / Size( G );
end
Now correct the error by adding a missing closing bracket,
read the file again and recalculate the average order of an element for M11
:
Read("avgord.g");
AvgOrdOfGroup(M11);
6.70846
Now let’s see an example of a warning. Since it is only a warning, it will redefine the function, and this may cause some unexpected result. To see what could happen, first edit the file to rollback the change in the type of the result (so it will return a rational instead of a float), and then comment out two lines as follows:
AvgOrdOfGroup := function(G)
# local sum, g;
# sum := 0;
for g in G do
sum := sum + Order(g);
od;
return sum/Size(G);
end;
Now, when you read the file, you will see warnings:
Read("avgord.g");
Syntax error: warning: unbound global variable in avgord.g line 4
for g in G do
^
Syntax error: warning: unbound global variable in avgord.g line 5
sum := sum + Order(g);
^
Syntax error: warning: unbound global variable in avgord.g line 5
sum := sum + Order(g);
^
Syntax error: warning: unbound global variable in avgord.g line 7
return sum/Size(G);
^
The reason GAP prints these warnings is the following. GAP distinguishes between so-called global and local variables. Global variables are known by “the whole session” so they can be accessed by every function and also changed by every function. Local variables are defined within functions and their values are only known as GAP is currently working in this function as we will see below. Whenever you need a new variable for a function you should always declare it as local. Otherwise, two things will happen: GAP will trow a warning and, more importantly, your code might have hard to decipher side effects you did not plan for.
These warnings mean because g
and sum
are not declared as local
variables, GAP will expect them to be global variables at the time when
the function is called.
Since they did not exist when Read
was called, a warning was displayed.
However, had they existed at that time, there would be no warning, and they may be still overwritten
during the call to AvgOrdOfGroup
! This shows how important is to
declare local variables. Let us investigate what happened in slightly
greater detail:
The function is now re-defined, as we can see from its output (or can
inspect with PageSource(AvgOrdOfGroup)
which will also display comments,
if any):
Print(AvgOrdOfGroup);
function ( G )
for g in G do
sum := sum + Order( g );
od;
return sum / Size( G );
end
but an attempt to run it results in an break loop:
AvgOrdOfGroup(M11);
Error, Variable: 'sum' must have an assigned value in
sum := sum + Order( g ); called from
<function "AvgOrdOfGroup">( <arguments> )
called from read-eval loop at line 24 of *stdin*
you can 'return;' after assigning a value
brk>
from which you can exit by entering quit;
.
What happens next demonstrates how things may go wrong:
sum:=2^64; g:=[1];
18446744073709551616
[ 1 ]
AvgOrdOfGroup(M11);
18446744073709604747/7920
sum; g;
18446744073709604747
(1,2)(3,10,5,6,8,9)(4,7,11)
So the variables g
and sum
we defined before running the functions
have now been changed. This is very often not what you intended to happen
and it might be hard to detect once your code becomes more complicated.
Finally, let us use our example to see the power of the break
loop.
Suppose our only change from the working code was commenting out
the initialisation of sum
. Now we start a new session and use
Read
to get our new definition of AvgOrdOfGroup
. No other
new global variables are now known.
AvgOrdOfGroup := function(G)
local sum, g;
#sum := 0;
for g in G do
sum := sum + Order(g);
od;
return sum/Size(G);
end;
Let this run:
AvgOrdOfGroup(MathieuGroup(11));
Error, Variable: 'sum' must have an assigned value in
sum := sum + Order( g ); called from
<function "AvgOrdOfGroup">( <arguments> )
called from read-eval loop at line 24 of *stdin*
you can 'return;' after assigning a value
brk>
GAP has entered the break
loop because something went wrong while running AvgOrdOfGroup.
While it is rather easy to find out what went wrong from the error message
in this case, it might be much harder to debug more complicated code.
The break
loop can be extremely helpful in this case as it “knows” local
variables (Note that there are currently no global variables G
or g
):
brk> G;g;
Group([ (1,2,3,4,5,6,7,8,9,10,11), (3,7,11,8)(4,10,5,6) ])
()
We use quit;
to leave the break loop.
Another instance in which you naturally encounter the break
loop is
when your code takes to long for your liking and you interrupt it:
Elements(SymmetricGroup(12));;
Once you hit enter on this command GAP starts a lengthy computation and you might want to wait this long. You can interrupt GAP in its computation by hitting Ctrl + C. GAP will then enter a break loop in which you can access the local variables of the currently executed functions to perhaps find out why the computation took so long.
Now, before reading the next part of the lesson, please
revert the last change by uncommenting the two commented lines, so that
you have initial version of AvgOrdOfGroup
in the file avgord.g
again:
AvgOrdOfGroup := function(G)
local sum, g;
sum := 0;
for g in G do
sum := sum + Order(g);
od;
return sum/Size(G);
end;
Paths
It is important to know how to specify paths to files in all operating systems and where to find your home and current directory.
It is useful to know that path and filename completion is activated by pressing Esc two or four times.
Key Points
Command line is good for prototyping; functions are good for repeated calculations.
Informative function names and comments will make code more readable to your future self and to others.
Beware of undeclared local variables!
The break loop can be extremely helpful for debugging.
You can interrupt computations by hitting Ctrl + C.