Self-Study Guide 2: Programming in Fortran 95 by Dr. Rachael Padman - HTML preview

PLEASE NOTE: This is an HTML preview only and some elements such as links or page numbers may be incorrect.
Download the book in PDF, ePub, Kindle for a complete version.

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.

img30.png

There are lots of things to notice about this example.

  • We have introduced a modified form of the do statement. Instead of looping a fixed number of times we loop while a condition is true. We shall return to this later.
  • The functions f and df are defined. They appear in the program after the word contains. contains marks the end of the main code of the program and indicates the start of the definition of functions and, as we shall see in a moment, subroutines.
  • Each function starts with the keyword function and ends with end function and the function name; this is quite like the syntax we have already met introducing the program itself.
  • Notice how within the function the function name is treated like a variable; we define its type and assign it a value; this is the value returned from the function and used when the function is called.
  • The functions defined in this way are called internal functions since they are defined within the program itself. We shall see the nature of this in a little while.
  • Values are supplied to the function via its arguments, i.e. the variables specified in parentheses (the x in this example); note each argument must have its type specified.
  • Enter this program, compile and run it. Try to understand the output. Modify the functions to some other form to find the roots of different functions using Newton’s method.

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:

img31.png

  • Compile this program and see what happens to the values typed in. You should find the values are interchanged.
  • Note how the subroutine is called and that its action is to modify its arguments.
  • One important difference between a function and subroutine is that a function must always have at least one argument, whereas a subroutine need not have any.

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

  • We say that these variables are global.

Here is a simple example:

img32.png

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:

img33.png

  • Here we have declared the array y to be of unknown size, but of an assumed shape; that is it is a vector and not a matrix. The intrinsic function size returns the number of elements in the array i.e. the length of the vector.
  • We can do the same for a two-dimensional array, a matrix. The corresponding definition would be:

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.

img34.png

  • Modify the setval example above to use this subroutine.
  • Deliberately introduce an error and within the subroutine attempt to change the value of the first argument; try compiling the program and see what happens.

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.