5. Program Organisation: Functions and Subroutines
In this section we will consider ways to structure your program, and in particular the use of functions and subroutines. We shall start off by assuming that the functions are to be defined in the same file as the main program. However this is rather cumbersome for a large program, and in the next section we will consider how to split the program between multiple files. Splitting the program in this way results in different program segments.
Subroutines and functions are the main way in which you can structure your F95 program. The main difference between them is that a function returns a value through its name, while the subroutine does not (a function must have at least one argument, while a subroutine may have none). The type of the function name must be declared both where it is defined and in the segment from which it is called. The function is used like any of the intrinsic functions while the subroutine is accessed via a call statement as we shall see below. We will use the term routine to refer to both subroutines and functions.
Direct communication between one program segment and a routine is through the arguments. Operations coded in the routine act directly on the variables referred to via the arguments in the calling statement. Other variables declared within the routine are local to that routine and not in general accessible elsewhere, or indeed in a subsequent call to the routine.
In what follows we will introduce functions and subroutines and then discuss how grouping functions and subroutines in different files can be an additional way of structuring your program.
5.1 Functions
We have already met the idea of functions in the form of intrinsic functions, i.e. functions which are part of F95 and which are used to calculate standard mathematical functions. We can also define our own functions for use in a program; this is a very powerful feature of all programming languages. Using functions has a number of advantages:
1. The code to implement the calculation can be written just once, but used many times.
2. The functions can be tested separately from the rest of the program to make sure they give the correct result; we can then be confident when using them in the larger program.
Let’s consider an example where we define two functions; in fact this is a simple program to find the root of an equation using Newton’s method.
The functions we define are for the mathematical function we are seeking the root of and its first derivative.
There are lots of things to notice about this example.
5.2 Formal definition
The structure of a routine is the same as for a main program, except that the first line defines the routine name. Thus the form for a function is:
function name(arg1, arg2, ....)
function name(arg1, arg2, ....)
[declarations, including those for the arguments]
[executable statements]
end function [name]
and for a subroutine:
subroutine name(arg1, arg2, ....)
[declarations, including those for the arguments]
[executable statements]
end subroutine [name]
Additionally for functions name must occur in a declaration statement in the function segment itself. The value the function takes is defined by a statement where name is assigned a value just like any other variable in the function segment.
The arguments must appear in the declarations as will any local variables; if you change the value of one of the arguments then the corresponding variable in the program “calling” the function or subroutine will also change. For this reason it is imperative that the type of dummy arguments agree with that of the variables in the call of the routine.
If you wish to terminate execution of a function or subroutine before the last statement in the subroutine (e.g. you may test some condition with an if clause and decide there is no more to do) then you can use the return statement to terminate the routine.
5.3 Subroutines
Subroutines are very similar to functions except that they do not return a value. Instead they have an effect on the program in which they are invoked by modifying the arguments to the subroutine. Here is a simple example which defines a subroutine called swap which swaps the values of its two arguments:
5.4 Local and global variables
Variables that are defined within a given routine (and are not arguments to the routine) are local to that routine, that is to say they are only defined within the routine itself. If a variable is defined in the main program then it is also available within any internal routine (i.e. one defined after the contains statement).
Here is a simple example:
This program is very contrived, it simply sets the value of the argument of the call to setval to the value of the variable a in the main program. In this example a is a variable global to the main program and the subroutine setval.
5.5 Passing arrays to subroutines and functions
Arrays can of course be used as arguments to subroutines and functions, however there are one or two special considerations.
5.5.1 Size and shape of array known
If the size and shape of the array are known, one can simply repeat the definition in the subroutine when the arguments are declared. For example, if v and r are vectors of length 3 a subroutine declaration in which they are passed would look like:
subroutine arr1(v, r)
real :: v(3)
real :: r(3)
and of course you could have used the alternative form using dimension instead:
subroutine arr1(v, r)
real, dimension(3) :: v, r
5.5.2 Arrays of unknown shape and size
The problem is how to deal with arrays of unknown length. This is important when for example we wish to write a subroutine which will work with arrays of many different lengths. For example, we may wish to calculate various statistics of an array. We can in this case use the following definition:
real, dimension(:,:) :: a2d
If you use this form for passing arrays and declare the subroutines in a different file from the calling program, it is essential that you use the concept of modules discussed in the section 6.
5.6 The intent and save attributes
We have seen how subroutines can modify their arguments, but often we will want some of the arguments to be modifiable and others just values supplied to the routine. Recall how we discussed the use of parameters as a way of protecting us from misusing something which should be a constant. In a similar way F95 provides a way of stating which arguments to a routine may be modified and which not. By default all arguments can be modified. The extra syntactic protection offered is by the intent attribute, which can be added to the declaration. It can have the values out, inout or in depending on whether you wish to modify, or not, the value of the argument in the routine call. This is another example of an option to a declaration statement (again c.f. the parameter definition earlier).
Here is the specification:
subroutine name(arg1, arg2, .....)
real, intent(in) :: arg1
real, intent(out) :: arg2
in the following example any attempt to change the variable x would result in a compiler error.
Local variables which are declared within a routine will in general not retain their values between successive calls to the routine. It is possible to preserve values after one return into the next call of the routine by specifying the save attribute in the declaration of that variable, e.g.
real, save :: local
This of course cannot be used for the arguments of the routine.