Fortran 90 introduced a data structure called modules that made a number of modern programming constructs easier to implement. Modules allow the user to easily incorporate funcionality from a package of routines into a new program. Modules also allow for better type-checking (i.e., it is easier to determine if you are calling the right subroutines with the right arguments). This brief tutorial will show you how to convert an old program over to the module format.
Consider the following definitions. Imagine they are part of a large project and exist in separate files.
subroutine swap(a, b)
implicit none
real, intent(inout) :: a, b
real :: c
c = a
a = b
b = c
end subroutine swap
program main
implicit none
real :: a, b
a = 3.
b = 7.
write(*,'(2(A,f3.1," "))')"a: ",a,"b: ",b
call swap(a,b)
write(*,'(2(A,f3.1," "))')"a: ",a,"b: ",b
end program main
Note the use of a couple of new Fortran 90 constructs, intent(inout) and ::, the declaration
delimiter. The first explicitly says that a and b will be modified within the procedure (function or subroutine) and
applies only to dummy arguments. The second is an easy, clean way to declare a number of variables. You will see me
use this exclusively. Also, note the use of the implicit statement, disallowing implicit variable
declaration. It is practically impossible to write readable code (and impossible to do anything interesting) without
this safeguard.
There is one minor issue with this arrangement: If you call swap with more than the requisite 2 variables,
you will not receive any errors, not even when the compiler goes through the linkage stage. To prevent that from
occuring, you can be paranoid and add an interface block to the main program:
program main
implicit none
real :: a, b
interface swap
subroutine swap(a,b)
real, intent(inout) :: a,b
end subroutine swap
end interface
a = 3.
b = 7.
write(*,'(2(A,f3.1," "))')"a: ",a,"b: ",b
call swap(a,b)
write(*,'(2(A,f3.1," "))')"a: ",a,"b: ",b
end program main
But that's a bit excessive to do for every procedure that you call from every subprogram. Note that argument checking is in effect if the subroutine is declared in the same file as the program. Another issue with this arrangement is that your namespace gets polluted by all of the subroutines that get linked with your program.
Enter the module. By encapsulating subroutines in modules, somehow the compiler gets much sharper and can help you identify your errors in the compile stage. To rewrite this simple example in module form:
module sub
implicit none
contains
subroutine swap(a, b)
real, intent(inout) :: a, b
real :: c
c = a
a = b
b = c
end subroutine swap
end module sub
program main
use sub
implicit none
real :: a, b
a = 3.
b = 7.
write(*,'(2(A,f3.1," "))')"a: ",a,"b: ",b
call swap(a,b)
write(*,'(2(A,f3.1," "))')"a: ",a,"b: ",b
end program main
There are a couple of nifty things in the new module. First of all, by saying implicit none at the
beginning of the module, you don't have to say it at the beginning of the subroutines. This is nice. Modules can also
hold data, declared, as in a program block, before the contains statement. Modules, unlike program blocks, cannot have
code that is not contained within a procedure. The contains statement is required. In this case, if you
try to call the swap subroutine with more than 2 arguments, the compiler alerts you to your error.
This might seem like nitpicking, but there are several important advantages to this approach.
- Code reusibility. A well-crafted module can form an important part in future projects.
- Type checking. Not only are the trivial examples in this document relevant, but if you start to try to pass procedures as arguments this will prove especially useful. This also allows for the creation of generic procedures that get called according to the type and number of arguments passed.
-
Replacing the COMMON block. Global variables are generally to be avoided; modules allow you to use them
when necessary in specific modules, optionally importing only certain procedures or variables with the
ONLYstatement. - Object oriented programming and custom types. While OOP isn't fully implemented in Fortran and is often a bad idea, it can be done when appropriate. Custom type definition is more useful.
In conclusion, approaching a projects with modules in mind is not any more difficult than the old method and offers a number of advantages. If compatibility with old systems is still a goal, perhaps modules will not serve you very well, but for new programs it can prove quite useful.