Fortran: Abstract Types and Deferred Procedures
With the new file_logger_t
type we were able to model a logger service as a derived type.
Now it’s time to make the service abstract. That is, to leave out the implementation detail that it’s a file logger (not a terminal output logger, etc.). Users should be able to use this abstract service to log any message, to whatever the application has been configured to log to.
An abstract derived type
Allowing a user to depend on an abstract service requires the use of an abstract
type. The user can import this abstract type in their module
or program
. Meanwhile, the factory function for the abstract logger will actually return the concrete file logger, which extends the abstract logger. For object-oriented programmers this may be a familiar approach, but it’s not a very common Fortran practice. In code it looks as follows:
Fortran: Modeling Services as Derived Types
We’ve seen how to define a derived type to represent a point in 2D space. We were able to add a type-bound procedure to it, and finally to make its components private and define a custom constructor for it.
In essence there are two categories of derived types:
- Derived types that hold some data. Their type-bound procedures are solely dealing with this data, and may produce valuable information with it. The point is an example of such a derived type: we can use its data to make geometrical calculations, or we can combine it together with other points into lines, polygons, etc. This all happens in memory. In typical business applications you would consider entities, value objects and data transfer objects (DTOs) to be in this first category.
- Derived types that can perform some task that deals with things external to the application, like a file, the terminal, a network connection, etc. Such a type doesn’t hold data inside. It is able to fetch data, or send/store data. In business applications such a type is often called a service.
In object-oriented programming, service types usually offer an abstraction. This allows the client to decouple from its specific implementation details. For example, take a logger service; it’s often beneficial for the user that they don’t have to be concerned about where the logs are written to (file, screen, log aggregation server, etc.). The user only wants to call a simple log()
function to log something to whatever has been configured at some higher level. Introducing a logger abstraction will take a few posts from here, but it’s good to know that service abstraction is the goal.
Fortran: Private Data Components and Custom constructors
In the previous post we have worked on the point_t
derived type, turning the module procedure distance
into a type-bound procedure:
module geometry
! ...
type :: point_t
real :: x
real :: y
contains
procedure :: distance => point_distance
end type point_t
contains
pure function point_distance(point_1, point_2) result(the_distance)
class(point_t), intent(in) :: point_1
! ...
end function point_distance
end module geometry
Private data components
Type-bound procedures can help us tie relevant behaviors (procedures) to derived types. It also allows us to let the derived type keep its data to itself. This is often called data hiding, or encapsulation of state. Nothing outside the module where the derived type is defined will have (read or write) access to its data components. This can be accomplished by adding the attribute private
to each data component:
Fortran: Type-bound Procedures
In the previous post we defined a derived type point_t
with real
data components x
and y
, to represent a point in 2D space. We also wrote a function to calculate the distance between two such points:
module geometry
implicit none(type, external)
private
public :: point_t
public :: distance
type :: point_t
real :: x
real :: y
end type point_t
contains
pure function distance(point_1, point_2) result(the_distance)
type(point_t), intent(in) :: point_1
type(point_t), intent(in) :: point_2
real :: the_distance
the_distance = sqrt((point_2%x - point_1%x)**2 + &
(point_2%y - point_1%y)**2)
end function distance
end module geometry
This allowed us to pass point_t
instances as function arguments to distance
, in another module or in the program
:
Fortran: Derived Types
We’ve seen types and variables and functions and subroutines. Now we can get to the next level and combine both variables and procedures in a so-called derived type.
Declaring a derived type and its data components
Object-oriented programming languages have classes. It took me some time to understand that classes can be seen as “extensions of the type system”. There are often primitive types like strings, integers, etc. and we can group them as properties of a class. Thereby, the class becomes some kind of advanced, composite type.
Fortran: Functions and Subroutines
So far we’ve put code inside the main program
block of our application. Soon the need arises to move blocks of code to a better place: a procedure with a proper name living inside a module
which groups related procedures by topic.
Fortran has two types of procedures:
- Functions, with arguments and a return value
- Subroutines, which are functions without a return value
Subroutines
Let’s define a new subroutine in a module
:
Fortran: Types and Variables
We’ve looked at the program
and module
blocks that can hold our code. Now we’ll find out where to put our data.
Declaration comes first
In Fortran, every time we want to store some value in a variable, we have to explicitly declare the type and the name of the variable first. This has to happen before any other executable statement, but after imports and other declarations like the implicit none
statement:
Fortran: Programs and modules
We have a basic Fortran application and a working environment to edit, compile and run it, thanks to FPM, IFX and Visual Studio Code. Let’s take a look at the structure of our very basic application.
The program
keyword
The minimum amount of code for a Fortran executable is this:
program the_program
! Do something here
end program the_program
Comment lines start with
!
. To write a multi-line comment, just repeat!
at the beginning of every line.
Running a simple Fortran program
In the previous post I included the obligatory “hello world” snippet. It would be nice if you could actually run code like that on your computer! It’s not so hard; we have very modern tooling available, and everything works equally well on Windows and Linux. The tools are freely available too.
First, install the following programs:
- Intel oneAPI Fortan Esstentials. This includes the IFX compiler.
- Fortran Package Manager (FPM). This tool is a “zero-configuration” build system which can also install and build dependencies for you.
- Visual Studio Code. We’ll use this as our editor. As far as I know there is no fully-integrated coding solution for Fortran programs. Some may use Visual Studio, some Visual Studio Code, and Jetbrains Clion may be a good option too. But none of these have solutions for all your IDE needs. After some experimenting, I think VS Code is the best option.
In a terminal, navigate to a place where you would like to create your Fortran project, and run:
Hello, Fortran world!
program hello_world
implicit none(type, external)
print *, 'Hello, world!'
end program hello_world
Since January 2024 I’m working with a smart group of programmers at Deltares. They create and maintain software in the complex “business” domains of hydrodynamics, morphology, water quality and ecology. Their software is used to understand and predict all kinds of phenomena related to water, by which they “Enable Delta life”. And that’s not just for the Netherlands (many of us here live below sea level or close to rivers); the software is used around the world, by governments and businesses.