Organising code structure#
Most programming languages allow you to collect commonly-used code into procedures that can be reused by calling them from other sections of code.
Fortran has two forms of procedure:
Subroutine: invoked by a
call
statementFunction: invoked within an expression or assignment to which it returns a value
Both subroutines and functions have access to variables in the parent scope by argument association;
unless the value
attribute is specified, this is similar to call by reference.
Subroutines#
The subroutine input arguments, known as dummy arguments, are specified in parentheses after the subroutine name; the dummy argument types and attributes are declared within the body of the subroutine just like local variables.
Example:
! Print matrix A to screen
subroutine print_matrix(n,m,A)
implicit none
integer, intent(in) :: n
integer, intent(in) :: m
real, intent(in) :: A(n, m)
integer :: i
do i = 1, n
print *, A(i, 1:m)
end do
end subroutine print_matrix
Note the additional intent
attribute when declaring the dummy arguments; this optional attribute signifies to the compiler whether the argument
is «»read-only»» (intent(in)
) «»write-only»» (intent(out)
) or «»read-write»» (intent(inout)
) within the procedure.
In this example, the subroutine does not modify its arguments, hence all arguments are intent(in)
.
It is good practice to always specify the
intent
attribute for dummy arguments; this allows the compiler to check for unintentional errors and provides self-documentation.
We can call this subroutine from a program using a call
statement:
program call_sub
implicit none
real :: mat(10, 20)
mat(:,:) = 0.0
call print_matrix(10, 20, mat)
end program call_sub
This example uses a so-called explicit-shape array argument since we have passed additional variables to describe the dimensions of the array
A
; this will not be necessary if we place our subroutine in a module as described later.
Functions#
! L2 Norm of a vector
function vector_norm(n,vec) result(norm)
implicit none
integer, intent(in) :: n
real, intent(in) :: vec(n)
real :: norm
norm = sqrt(sum(vec**2))
end function vector_norm
In production code, the intrinsic function
norm2
should be used.
To execute this function:
program run_fcn
implicit none
real :: v(9)
real :: vector_norm
v(:) = 9
print *, 'Vector norm = ', vector_norm(9,v)
end program run_fcn
It is good programming practice for functions not to modify their arguments—that is, all function arguments should be
intent(in)
. Such functions are known aspure
functions. Use subroutines if your procedure needs to modify its arguments.
Modules#
Fortran modules contain definitions that are made accessible to programs, procedures, and other modules through the use
statement.
They can contain data objects, type definitions, procedures, and interfaces.
Modules allow controlled scoping extension whereby entity access is made explicit
Modules automatically generate explicit interfaces required for modern procedures
It is recommended to always place functions and subroutines within modules.
Example:
module my_mod
implicit none
private ! All entities are now module-private by default
public public_var, print_matrix ! Explicitly export public entities
real, parameter :: public_var = 2
integer :: private_var
contains
! Print matrix A to screen
subroutine print_matrix(A)
real, intent(in) :: A(:,:) ! An assumed-shape dummy argument
integer :: i
do i = 1, size(A,1)
print *, A(i,:)
end do
end subroutine print_matrix
end module my_mod
Compare this
print_matrix
subroutine with that written outside of a module. We no longer have to explicitly pass the matrix dimensions and can instead take advantage of assumed-shape arguments since the module will generate the required explicit interface for us. This results in a much simpler subroutine interface.
To use
the module within a program:
program use_mod
use my_mod
implicit none
real :: mat(10, 10)
mat(:,:) = public_var
call print_matrix(mat)
end program use_mod
Example: explicit import list
use my_mod, only: public_var
Example: aliased import
use my_mod, only: printMat=>print_matrix
Each module should be written in a separate
.f90
source file. Modules need to be compiled prior to any program units thatuse
them.
Optional arguments#
An advantage of placing subroutines and functions in modules is that they can have optional
arguments. In a procedure with an argument declared optional, the present
function is used to test if the argument was set in the caller. Optional arguments that are not present may not be accessed within the procedure. Here is a generalization of the vector_norm
function that can use powers other than 2 to compute the Lp norm.
module norm_mod
implicit none
contains
function vector_norm(vec,p) result(norm)
real, intent(in) :: vec(:)
integer, intent(in), optional :: p ! power
real :: norm
if (present(p)) then ! compute Lp norm
norm = sum(abs(vec)**p) ** (1.0/p)
else ! compute L2 norm
norm = sqrt(sum(vec**2))
end if
end function vector_norm
end module norm_mod
program run_fcn
use norm_mod
implicit none
real :: v(9)
v(:) = 9
print *, 'Vector norm = ', vector_norm(v), vector_norm(v,2)
print *, 'L1 norm = ', vector_norm(v,1)
end program run_fcn