Procedures again: interfaces
Last updated on 2026-02-21 | Edit this page
Overview
Questions
- What is an interface and why might I need to write one?
- Can I use a function or a subroutine as an argument to another procedure?
- How can I write a procedure that will accept arguments of different types?
- Can I overload arithmetic and relational operators to work with my derived types?
- Can I write a function that will accept both scalar and array arguments?
Objectives
- Understand the need for interfaces and recognise when they are necessary.
- Be able to write an interface to allow the passing of a procedure as an actual argument.
- Understand and be able to implement overloading and polymorphism.
- Understand what an
elementalfunction is and be able to write one.
So far, we have demanded that all procedures be defined as part of a
module. This has the important advantage that it makes the
interface to the function or subroutine explicit to
the compiler, and to other program units via the use
statement. The compiler can then check the order and type of the actual
arguments.
Other situations arise where we may need to give the compiler additional information about the interface.
An external function
Consider a program unit (which may or may not be a separate file) which contains a function declaration outside a module scope. E.g.,
FORTRAN
function my_mapping(i) result(imap)
integer, intent(in) :: i
integer :: imap
...
end function my_mapping
If we compile a program which includes a reference to this function, an error may result. We have not given the compiler any information about how the function is meant to be called.
It is possible to provide the compiler with some limited information
about the return value of the function with the external
attribute (also available as a statement). E.g.,
However, we have still not given the compiler full information about
the interface. The interface is said to remain implicit. To
make it explicit, the interface construct is available:
FORTRAN
program example
implicit none
interface
function my_mapping(i) result(imap)
integer, intent(in) :: i
integer :: imap
end function my_mapping
end interface
...
This has provided a full, explicit, statement about the interface of
the function my_mappping(). (Note that the dummy argument
names are not significant, but the function name and the argument types
and intents are.)
The compiler should now be able to check the arguments are used correctly in the calling program (as if we had declared the function in a module).
Interface blocks are necessary in other contexts.
Exercise (5 minutes)
Externals and interfaces
To illustrate the points made above, a very simple external function is defined in the file external.f90, and an accompanying program example1.f90 calls the function therein.
Try to compile the two files, e.g.:
What happens?
Try adding the appropriate external declaration
What happens if you try to compile the program now? (Note at this
point that there are no modules involved, so no .mod files
will appear).
Then try running the program. Is the output what you expect?
Finally, remove the external declaration and try to
introduce the correct interface block. What happens
now?
Without either the external declaration of the function
or an interface, the compilation will simply fail. gfortran
produces the following:
OUTPUT
example1.f90:24:33:
24 | print *, "The array size is: ", array_size(a), size(a)
| 1
Error: Function 'array_size' at (1) has no IMPLICIT type
Adding the external declaration allows compilation to
succeed, but the following output is produced on running the
program:
OUTPUT
The array size is: 0 6
The function call returns an incorrect value of 0 where the (3,2) array has an actual size of 6.
Removing the external declaration and adding an interface matching
the function in external.f90 as follows will help (and you
may have already spotted the issue):
FORTRAN
interface
function array_size(a) result(isize)
real, dimension(:), intent(in) :: a
integer :: isize
end function array_size
end interface
Compiling now produces an error again, as reported here by
gfortran:
OUTPUT
example1.f90:23:45:
23 | print *, "The array size is: ", array_size(a), size(a)
| 1
Error: Rank mismatch in argument 'a' at (1) (rank-1 and rank-2)
In the main program we were attempting call array_size()
on a rank two array, while in external.f90 we implemented
it for rank one arrays. Now that we have an interface, the compiler can
see that the way we’re calling the function is incorrect, so it produces
the error and fails compilation.
This is preferable to the external declaration by which
we essentially asked the compiler to trust that it can call
array_size() however we tell it to do so, even though it
was in this case incorrect.
Passing functions or subroutines as arguments
Sometimes it is convenient to provide a procedure as an argument to another function or subroutine. Good examples of this are for numerical optimisation or numerical integration methods where a user-defined function needs to be evaluated for a series of different arguments which cannot be prescribed in advance.
This can be done if an interface block is provided which describes to the calling procedure the function that is the dummy argument. E.g.,
FORTRAN
subroutine my_integral(a, b, afunc, result)
real, intent(in) :: a
real, intent(in) :: b
interface
function afunc(x) result(y)
real, intent(in) :: x
real :: y
end function afunc
end interface
...
The function dummy argument has no intent. One cannot in general use intrinsic procedures as actual arguments.
Limited polymorphism
We have seen a number of intrinsic functions which take arguments of
different types, such as mod(). Such a situation where the
arguments can take a different form is sometimes referred to as limited
polymorphism (or overloading).
However, it is not possible in Fortran to define two procedures of the same name, but different arguments (at least in the same scope). We need different names; suppose we have two module sub-programs, schematically:
FORTRAN
subroutine my_specific_int(ia)
integer, intent(inout) :: ia
... integer implementation ...
end subroutine my_specific_int
subroutine my_specfic_real(ra)
real, intent(inout) :: ra
... real implementation ...
end subroutine my_specific_real
A mechanism exists to allow the compiler to identify the correct routine based on the actual argument when used with a generic name. This is:
FORTRAN
interface my_generic_name
module procedure my_specific_int
module procedure my_specific_real
...
end interface my_generic_name
This should appear in the specification (upper) part of the relevant module. The two specific implementations must be distinguishable by the compiler, that is, at least one non-optional dummy argument must be different.
Exercise
An interface for polymorphic PBMs
In the earlier episode on I/O, we wrote a
module to produce a .pbm image file. The accompanying
module pbm_image.f90
provides two implementations of such a routine: one for a logical array,
and another for an integer array.
Check you can add the appropriate interface block with
the generic name write_pbm to allow the program example3.f90
to be compiled correctly.
We need to add an interface to the module specification for
write_pbm which allows use of both the
write_logical_pbm and write_integer_pbm
subroutines. You should be able to follow the description above to do
so. The only minor complication is that the module uses
private by default; unless we specify public
for the new generic interface, it will remain hidden from the main
program.
Putting this together, the following should allow you to compile and run the program with no errors:
FORTRAN
public :: write_pbm
interface write_pbm
module procedure write_logical_pbm
module procedure write_integer_pbm
end interface
With this, the internal workings of the module are hidden away and
its use is entirely via write_pbm.
Operator overloading
For simple derived types it may be meaningful to define relational and arithmetic operators. For example, if we had a date type such as
it may be meaningful to ask whether two dates are equal and so on (it would not really be meaningful to add one date to another).
One can write a function to do this:
FORTRAN
function my_dates_equal(date1, date2) result(equal)
type (my_date), intent(in) :: date1
type (my_date), intent(in) :: date2
logical equal
! ...
end function my_dates_equal
As a syntactic convenience, it might be useful to use ==
in a logical expression using dates. This can be arranged via
Again this should appear in the relevant specification part of the
relevant module. Such overloading is possible for relational operators
==, /=, >=,
<=, > and <. If
appropriate, overloading is also available for arithmetic operators
+, -, *, and /.
It is also possible to overload assignment =.
Elemental functions
Again, we have seen that some intrinsic functions allow either scalar or array actual arguments. The same effect can be achieved for a user-defined function by declaring it to be elemental. The procedure is declared in terms of a scalar dummy argument, but then may be applied to an array actual argument element by element.
Such a procedure should be declared:
FORTRAN
elemental function my_function(a) result(b)
integer, intent(in) :: a
integer :: b
! ...
end function my_function
An invocation should be, e.g.:
All arguments (and function results) must conform. An elemental
routine usually must also be pure.
Exercise (5 minutes)
Elemental conversion from logical to PBM
In the pbm_image.f90
module there is a utility function logical_to_pbm() which
is used in write_logical_pbm() to translate the logical
array to an integer array. Refactor this part of the code to use an
elemental function.
The function logical_to_pbm() is currently
pure and takes in a scalar logical lvar to
return the scalar integer ivar. The function is currently
pure. Everything is already in place to convert the
function to elemental:
FORTRAN
elemental function logical_to_pbm(lvar) result (ivar)
! Utility to return 0 or 1 for .false. and .true.
logical, intent(in) :: lvar
integer :: ivar
ivar = 0
if (lvar) ivar = 1
end function logical_to_pb
All that remains is to have write_logical_pbm() call
this new elemental version. Further down in that function
you will see the old nested loop to move through the map
array and from it use logical_to_pbm() to fill the
imap array:
FORTRAN
do j = 1, size(map, dim = 2)
do i = 1, size(map, dim = 1)
imap(i,j) = logical_to_pbm(map(i,j))
end do
end do
With the new elemental version of
logical_to_pbm(), we can replace this entire structure with
the single line:
Exercise (20 minutes)
Integrating a function
Write a module/program to perform a very simple numerical integration of a simple one-dimensional function f(x). We can use a trapezoidal rule: for lower and upper limits a and b, the integral can be approximated by
(b - a)*(f(a) + f(b))/2.0
We can go further and split the interval between a and
b into small sections of size h = (b - a)/n.
In a similar manner, approximating the integral of each small section
with a trapezium allows us to estimate the total integral to be
h*(f(a) + sum + f(b))/2.0
with the sum
sum = 0.0
do k = 1, n-1
sum = sum + 2.0*f(a+k*h)
end do
Write a procedure that will take the limits a and
b, the integer number of steps n, and the
function, and returns a result.
To check, you can evaluate the function cos(x) sin(x)
between a = 0 and b = pi/2 (the answer should
be 1/2). Check your answer gets better for value of
n = 10, 100, 1000.
A sample solution is provided in integral_program.f90 and integral_module.f90.
- An interface provides important information about a procedure to the compiler. Without this information, the compiler may not be able to use it.
- When using modules, interfaces are generated automatically and provided to the compiler.
- If not in a module, a function can be made visible to the compiler
by declaring it with the
externalattribute, but the compiler can go further by checking the argument types if you write a full, explicitinterfaceblock. - Interfaces can also be used to implement limited polymorphism and operator overloading for derived types.
- An elemental function is one which can be applied to a scalar actual argument or element-by-element to an array actual argument.