Is creating nested subroutines/functions considered good practice in Fortran?

Over time I’ve come to believe that callbacks, procedure arguments and procedure pointers are under-utilized by Fortran practitioners. The majority of Fortran codes remain written in a procedural style.

If we think about which codes have lasted long – ODEPACK, MINPACK, COBYLA, SLSQP, ARPACK … – and are reused by hundreds of programmers including those from other programming languages, what many of these program have in common is they accept a procedure parameter or provide a reverse communication interface. Both of these are a form of dependency injection. The reasons these programs are long-lasting is because they help achieve separation of concerns. A numerical expert writes an algorithm; the users just need to provide their problem and receive immediate value from such programs.

Rob Pike, a famous C programmer, has captured this essence of function pointers (procedure arguments in Fortran) in his Notes on Programming in C (pg. 5),

Another result of the tyranny of Pascal is that beginners don’t use function pointers. (You can’t have
function-valued variables in Pascal.) Using function pointers to encode complexity has some interesting
properties.

Some of the complexity is passed to the routine pointed to. The routine must obey some standard
protocol — it’s one of a set of routines invoked identically — but beyond that, what it does is its business
alone. The complexity is distributed.

There is this idea of a protocol, in that all functions used similarly must behave similarly. This
makes for easy documentation, testing, growth and even making the program run distributed over a network — the protocol can be encoded as remote procedure calls.

I argue that clear use of function pointers is the heart of object-oriented programming. Given a set of
operations you want to perform on data, and a set of data types you want to respond to those operations, the
easiest way to put the program together is with a group of function pointers for each type. This, in a nutshell, defines class and method. The O-O languages give you more of course — prettier syntax, derived
types and so on — but conceptually they provide little extra.

Combining data-driven programs with function pointers leads to an astonishingly expressive way of
working, a way that, in my experience, has often led to pleasant surprises. Even without a special O-O
language, you can get 90% of the benefit for no extra work and be more in control of the result. I cannot
recommend an implementation style more highly. All the programs I have organized this way have survived comfortably after much development — far better than with less disciplined approaches. Maybe
that’s it: the discipline it forces pays off handsomely in the long run.

When I think of scientific computing, here are some of the callbacks which immediately come to my mind:

  • scalar functions (root solving, integration, fitting)
  • multivariate functions (systems of nonlinear equations, systems of ODEs, integration)
  • matrices (typically a Jacobian of some sort, or a sparsity pattern)
  • operators (a transformation returning a vector, e.g. a matrix-vector multiply)

Through composition, you can arrive at quite complex cases. For example you may need to solve a boundary value problem (BVP). For this you may use the shooting method where you combine an ODE solver and a root-finder. Afterward you’d like to fit the BVP to experimental data using a least squares procedure. In this case you will have a hierarchy of callbacks working in sync.

A quite advanced example of callback usage is the Unified Framework for Finite Element Assembly (also known as UFC), which is an interface between the problem-specific and general purpose components of a finite element program used in the FEniCS project. Although written in C and C++, it allows one to provide a set of callback functions to assemble the bilinear and linear forms originating from the weak form of the PDE. The general parts including assembly and solving the system of equations is handled in the C++ framework.

In other fields of computing callbacks are used for all sorts of things:

  • comparison functions for sorting (see qsort in C)
  • drawing and GUI callback functions
  • audio waveforms (see Open Sound Control - #2 by ivanpribec)
  • teardown functions (see atexit in C)
  • games (switching between different behaviors, e.g. spells or weapons)
3 Likes