Modules again
Overview
Teaching: 10 min
Exercises: 10 minQuestions
Objectives
Modules
The introductory course encouraged the use of modules to provide structure, and to separate public interface from private implementation. Schematically, we have seen:
module schematic
! Broadly, public "interface"
contains
! Broadly, private "implementation"
end module schematic
However, …
We also have a situation in which the (public) interface can remain unchanged, while a change to the (private) implementation requires recompilation of the module. This may have knock-on effects requiring re-compilation of any dependencies. Is this really necessary if we have not changed the interface?
The answer should be no. However, a typical makefile dependency might
cause re-compilation of all units use
-ing the module schematic
.
Some (rather baroque) mechanisms have been used to avoid this, e.g.,
making a copy of the .mod
file, and interrogating whether this changes
on compilation.
The separation of large modules into parts may be desirable, but can
necessitate the proliferation of public entities which might not
really be wanted. Cross-dependencies can also be a problem (the
situation where module a
wants to use module b
and vice-versa).
The is not quite satisfactory. A clearer separation between interface and implementation (cf. C header files) is required.
Submodules
Submodules may be organised in a tree-like hierarchy, with the ancestor module as the root.
module example_module
end module example_module
The module should define only the interface (there should probably be
no contains
statement).
The implementation can then be placed in a submodule:
submodule (example_module) example_submodule
contains
end submodule example_submodule
Note: 1. the submodule automatically has access to the declarations in
the example
module via host association. There is no need to use
the ancestor module.
Formally, we have:
submodule (parent-identifier) submodule-name
[ specification part ]
[ module-subprogram-part]
end [submodule [ submodule-name] ]
where the parent-identifier
is name of the parent module.
Submodule procedures
Suppose in our module we define a new data type:
type, public :: example_t
...
end type example_t
We can define an interface block using this type (also in the module) to specify the contract:
interface example_t
module function example_int_t(ival) result(e)
integer, intent(in) :: ival
type (example_t) :: e
end function example_int_t
end interface example_t
Note the module
at the start of the function
declaration, and that
there is no import
statement required in the interface block.
This would then be implemented in the submodule
subprogram part:
module function example_int_t(ival) result(e)
integer, intent(in) :: ival
type (example_t) :: e
...
end function example_t
The interface and the function declaration must match precisely (only the name of the result variable is not formally part of the interface and need not match).
Exercise (2 minutes)
Compile (do not link) the module
and submodule files example.f90
and
example_a.f90
in the current directory. E.g.,
$ ftn -c example_module.f90
$ ftn -c example_submodule.f90
Check what additional files have been generated (the exact details
will depend on the compiler: some produce .smod
files, others
just additional .mod
files).
Abbreviated form
It’s possible to omit the interface details in the submodule. E.g.,
module procedure example_t
...
end procedure example_t
Note the use of procedure
here. Again, the details must match the
interface declared in the module.
While this provides some saving in verbosity, it may be preferable to keep the full form.
Host association and use association
A submodule has access to entities in the parent module automatically
by host association, but may also use
other modules in the standard
way.
A submodule of module a
can access module b
by use
association.
In addition, a submodule of module b
can access module a
. This
breaks the circular dependency problem in standard modules.
Submodule declarations
Note that submodules do not contain public
or private
declarations.
However, additional variables, types, or named constants may be defined
in a submodule specification part. These are neither public nor private,
but can only be accessed in the submodule and any descendants by host
association.
Comment
Submodules may only really come into their own if you have an extremely large software project for which the overheads of re-compilation are high, or the organisation of extremely large single modules becomes a problem. For small exercises, such as those presented in this course, the additional files involved are probably an unnecessary distraction. So we will only use them on one occasion.
Exercise (15 minutes)
We will take the single module developed for the file_writer_t
and split
it into a module containing the abstract definition, and one or two
implementations. A client program should then only depend on the top-level
(abstract) interface.
It’s probably easiest to make a number of copies of a working solution module, and then delete unwanted parts from each. Suggested procedure:
- Keep the abstract type
file_writer_t
definition and its related interface block infile_module.f90
. Here we will also need an interface for thefile_writer_from_string()
function. Check the newfile_module.f90
compiles on its own. - Make a new (ordinary) module
file_unformatted
for the unformatted implementation in a separate file, and a new modulefile_formatted
for the formatted implementation. - Add a submodule to
file_module
to hold the implementation of thefile_writer_from_string()
function in s separate file.
An example program which only has use file_module
will now depend only
on the public definition of the abstract type.
All the Fortran sources should be compiled together to produce an executable.
What further decomposition into submodules could we make at this point?
Key Points