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()
: