Below you will find pages that utilize the taxonomy term “Fortran”
Fortran - Errors and error handling - Part 3 - Preventing edge cases with types
In the previous post we introduced an optional_real_t
type to be the return value of an average
function, because depending on the size of the array of numbers passed to it this function may or may not return a real
value.
What if we could just prevent people from calling the average
function when they don’t have any numbers? Maybe we’re lagging too far behind if we have to verify the size of the array inside the average
function.
Fortran - Errors and error handling - Part 2 - Optional results
In the previous post we’ve looked at several ways to communicate to the user of a function that something could go wrong, or that maybe the function couldn’t produce the result that the user was looking for. In the case of an average function, we’d want to communicate to the user that in some cases the function can’t produce a meaningful result, namely if the array of numbers is empty. This isn’t really an error, it’s just that we don’t have a way to answer the question “what’s the average of this array?”. To communicate that with the function signature, we tried adding a “success” return value, making the calculated average an intent(out)
argument. The function modifies the variable passed by the user to contain the average value, but only if the array is not empty:
Fortran - Errors and error handling - Part 1 - Exploration
Fortran doesn’t have exceptions. You can’t jump out of a function when something unexpected happens, then reflect on what happened outside the function, with the help of a try/catch block. As an alternative, intrinsic (built-in) procedures and constructs like write
, read
and open
allow you to pass an integer
variable as one of its arguments, to which it will assign a specific value indicating whether an error occurred. For example, when you try to open a file that doesn’t exist, you will get a non-zero value in the variable passed as the argument for iostat
:
Fortran - Functional Programming Concepts - Reduce
We’ve implemented filter and map operations. The results of both these operations are new arrays. Yet another common operation on arrays is the so-called reduction operation; we “reduce” multiple values to a single value. The most obvious example is calculating the sum of an array of integers: the input is an array (or list) and the output is a single integer
:
pure function sum_all(numbers) result(res)
integer, dimension(:), intent(in) :: numbers
integer :: res
integer :: i
res = 0
do i = 1, size(numbers)
res = res + numbers(i)
end do
end function sum_all
Just like with filter
and map
we can imagine a generalization of this process, which involves looping over the array elements and building up a single return value along the way. We start with the existing sum_all
function. The first step is to let the user decide what the return value should be, which is the logic that happens inside the do
loop. We extract a function for this:
Fortran - Functional Programming - List type
In the previous post we looked at this dream scenario:
list([1, 3, 5, 7]) % filter(divisible_by(3)) % map(square)
As mentioned, we can’t chain function calls like this. But we can put intermediate results in local variables and achieve something similar:
integers = list([1, 3, 5, 7])
divisible_by_3 = integers%filter(divisible_by(3))
squared = divisible_by_3%map(square)
If the intermediate values are all the same type, we can reuse the variable:
integers = list([1, 3, 5, 7])
integers = integers%filter(divisible_by(3))
integers = divisible_by_3%map(square)
To enable this syntax, it’s clear that we need to introduce a derived type for a list of integers that has type-bound procedures filter
and map
. These functions should each return a new list, so we can call any of these functions again on the new list. Let’s start with an integer_list_t
which contains an array of integers. We also provide a generic list
interface
with a specific procedure create_integer_list
for creating the integer_list_t
:
Fortran - Functional Programming Concepts - Map
We considered the problem of filtering elements in an array, keeping only the ones we want. A decision about that is made by a function we pass as an argument.
A similar problem, but with its own challenges, is when we have an array of values and want to create a new array, with an equal number of elements, but each element has been transformed in some way. For instance, say we have an array of integers and want to square each integer. A procedural approach looks as follows:
Fortran - Functional Programming Concepts - Closures
We have a reusable filter
function to which we pass the name of a procedure that matches a given interface
. But can we also do something like this?
filter([1, 2, 3, 4], is_divisible_by(3))
Closures
This is a very common thing in functional programming. It involves something called higher-order functions. This means that a function may itself return a function. In this case, is_divisible_by
returns a function that can be passed to filter
. This returned function also needs to remember the value 3 that was passed to it. When it’s executed for each value in the integer array, it will check if that value is divisible by 3. In other languages such a function is called a closure; it’s a function that can be passed around as a value. Besides the function itself, it also has the data from the context where it was created - “bound” to it. In “ideal Fortran”, we would write something like this:
Fortran - Functional Programming Concepts - Generic Filtering
We have a working filter
function for integers. Now can we generalize it? It would be nice if we could filter other types of arrays, like real
arrays.
Generic filtering
As mentioned before, Fortran doesn’t have support for generics, which would allow you to use some kind of “placeholder” for types (e.g. [value_type]
):
pure function filter(values, filter_func) result(res)
[value_type], dimension(:), intent(in) :: values
interface
pure function filter_func(value) result(keep)
implicit none(type, external)
[value_type], intent(in) :: value
logical :: keep
end function filter_func
end interface
[value_type], dimension(:), allocatable :: res
integer :: i
res = pack(values, [(filter_func(values(i)), i=1, size(values))])
end function filter
You could then use filter
on an array with any type of value, and the filter_func
’s value
argument would have to be of that same type, and the return type would be of that type too.
Fortran - Functional Programming Concepts: Filter
Once introduced to Fortran, we are left wondering what kind of language it really is. Traditionally, it’s been used as a procedural language, or a “Do this, do that” language as I’d like to call it. You can do some Object-Oriented programming with it, but it’s somewhat clunky, verbose, and not very “idiomatic”. (That’s not to say that you shouldn’t do it!) The same goes for Functional programming. If we’d like to write Fortran code that follows this paradigm, it becomes even more awkward. You’ll need several advanced techniques to make it work, and then there are still many limitations. The inability to chain function calls, the lack of tail call optimization and generic type-support, and the obstacles you have to conquer to get some kind of delayed execution; all of these sound like good reasons for not-even-trying…
Fortran: Enumeration, part 3
In the previous post we properly encapsulated the integer
level with a log_level_t
derived type, achieving type safety.
Now, let’s consider the following feature request: we want to show the log level in front of the message. We could do it with decoration of course, but in the scope of this article, let’s do it in the log()
subroutine directly. In this case we’re “lucky” that we can access the module-private data component level
, so we can do a select case
on it to determine what string should be printed:
Fortran: Enumeration, part 2
In the previous post we introduced a derived type log_level_t
. The type of data passed to the log()
procedure is still an integer
though:
subroutine log(message, level)
character(len=*), intent(in) :: message
integer, intent(in) :: level
! ...
end subroutine log
This doesn’t stop anyone from passing a completely meaningless integer
value as the level
argument to log()
.
Increasing type-safety
We’d like to increase type-safety and the way to do it is by using a more specific type. In our case, it would be great if we could use log_level_t
as the argument type for level
:
Fortran: Enumeration, part 1
In the post about decoration we declared log levels as follows:
module logging_base
! ...
integer, parameter, public :: LOG_DEBUG = 0
integer, parameter, public :: LOG_INFO = 1
integer, parameter, public :: LOG_WARN = 2
integer, parameter, public :: LOG_ERROR = 3
integer, parameter, public :: LOG_FATAL = 4
! ...
end module logging_base
This is somewhat useful, but requires a dedicated only
mention when importing each of these constants, which isn’t very nice:
program main
use logging_base, only: log, LOG_DEBUG, LOG_WARN
! ...
call log('A debug message', LOG_DEBUG)
! ...
call log('A warning', LOG_WARN)
end program main
Although we could live with that, the bigger issue remains: this is not a type-safe solution. Any integer
may be passed as an argument for level
, and the compiler wouldn’t warn us. For example:
Fortran: Module Design
Fortran projects are famous for their large modules. Actually, we may also find very large files in legacy projects written in other languages, like Java, PHP, etc. These projects sometimes have files with thousands of lines of code. Fortran projects suffer more from large files I’d say, because:
- IDE support is not great. It’s often still hard to navigate to definitions or usages. There isn’t any support for automated refactorings, like extract function, move function, add parameter, etc. You often can’t get a clear overview of the structure/outline of a file.
- There are no strict indentation rules, making it easy to create a complete mess. Not everyone uses code formatting tools. Code formatting tools that exist and are used, aren’t always very good or easily configurable.
- Fortran code can be hard to read, partially because a lack of curly braces. It’s hard to visually recognize blocks of code. Another reason is that commonly there’s no distinction in letter casing between type names, function names, variable names, etc. Other languages may use CamelCase for types, CAPS for constants, pascalCase for methods, snake_case for functions, etc. In Fortran code, it’s all snake_case.
If there’s anything we can do to make Fortran code easier to deal with, it’s to split the code into smaller parts. We need to have many more modules, each with distinct topics and concerns, so we can get a quick overview of the application’s domain and capabilities. Additionally, this will help us find the right place when we want to make changes to the code.
Fortran: Service Composition, part 2: Decoration
In the previous post we saw how to use an abstraction to compose an aggregation of services of that same abstraction. There we simply delegated a call, adding no specific behavior. But we might as well add some new behavior before or after delegating the call.
As an example, let’s try to implement a new feature in the logging module: we want to add a timestamp in front of each message, so the output becomes something like this:
Fortran: Service Composition, part 1: Aggregation
With our new service abstraction logger_t
we are able to easily implement more features for our logger. We can do this without affecting current user code, because their code relies only on the abstraction, never on concrete implementations.
A terminal output logger
One thing we can do is define a second logger type, one that prints each message to the terminal output (stdout
). Being a logger, it should also extend the abstract logger_t
type, and provide an implementation for log()
:
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.