2
$\begingroup$

So I just picked up a project that is written in fortran90. I am used to coding in python and C.

What is really troubling for me is the use of subroutines in fortran90. In fortran people use subroutines to set variables of the parentscope in a sub function. This is really intransparent for me since tracking changes of those variables gets really difficult if the code becomes more complex. What I mean is this, compare these snippets:

subroutine big()
    implicit none
    integer :: a

    a=1
    call f()
    print *,a

    contains

      subroutine f()
      implicit none
        a = 2
      end subroutine f

end subroutine big

program hello
    call big()
end program hello

2

Similarly in python I am protected from overwriting a:

def big():
  a=1
  def f():
    a=2

  f()
  print(a)

big()

1

In python I cannot simply set a in a subfunction (except I use a global statement which is bad practice), in fortran I can and people are doing it.

  • What is the proper way/ good coding practice to write modern fortran code where parent scope variables are protected?
$\endgroup$
  • $\begingroup$ As a start, I would consider it good practice to pass the values which are used as parameters to the subroutine. That self-documents that something might happen to the argument, and that it is needed/used within. I would also consider it best practice to refrain from having subroutines within your routines. If I remember correctly you may sort bigger chunks of functionality into modules in fortran and explicitly state what you want to use from those (similar to NameSpace::function() in C/C++) $\endgroup$ – MPIchael Oct 11 at 14:07
4
$\begingroup$

There are several tools to make a codebase more structured. In your example, the subroutine within big() is called without arguments, but changes something in the outer scope, as you said. To prevent something like this, you may of course explicitly state the argument to the function:

subroutine big()
  implicit none
  integer :: a
  a=1
  call f(a)
 print *,a

 contains
   subroutine f( argument )
     implicit none
     type(Complex), intent(inout) :: argument
     argument = 2
   end subroutine f
end subroutine big

This shows the reader that something will happen to a! There are also explicit intent statements ready to specify what kind of arg argument actually is. (Is it only an input which should not be changed?!)

Generally speaking the more you expose the -intent- of what the code is supposed to be doing, the easier you make it for the reader to work with it.

There is also the module system, where you may sort different subroutines/functions into a module, and thereby structure your code into different segments. What these segments are depends strongly on your case, but using modules may greatly clearify what the subroutine is supposed to be doing, without even looking at it.

lets say you sorted all your linear algebra stuff into a module called linAlg. You may then use:

use linAlg, only : vectorAddition,scalarMultiply

in some other place, and the reader will have enough hints to get whats going on.

So my two cents of keeping a monstrosity of a fortran codebase at bay is:

  1. encapsulate into comparatively small modules to keep namespaces small and self-document your intent with each module (linear_algebra_mod, finiteElement_mod,solvers_mod, vectorTypes_mod,matrixTypes_mod, inputOutput_mod etc.). Once you did that, you can then explicitly state what you want to use out of them, eg.: "use a_mod, only : foo, bar"
  2. explicitly state the inputs/outputs of subroutines and functions and make use of the intent statements
  3. Use concise but meaningful subroutine/function names which are self-explanatory (If there is to much happening in one sub, then split it up into smaller parts)
  4. use expressive variable names. Do not go down the road of naming your variables: i, ii, ij, ji, ijk, ikk, ikkijasdf :-)
  5. Use a reasonable ammount of input checks within important subroutines. If your method gets lots of NaN's as an input, or you try to calculate the square root of -1.0, you better be informed right there and not after a 3 hour debugging session!
  6. Very important: avoid premature optimization. Just because the nerd in your department knows all the tricks to tickle out another 10% of perf doesn't mean that readability of your codebase is irrelevant. Focus on performance only when you actually run into problems.

Depending on how big your project is, you may also port it to even newer versions of Fortran (2008), where you can encapsulate into classes! (OOP in Fortran)

$\endgroup$
  • $\begingroup$ This is, in fact, good advice for any programming language! $\endgroup$ – Wolfgang Bangerth Oct 14 at 2:11
  • $\begingroup$ what changes if I have access to Fortran 2008? Is the problem I have mentioned obsolete then? $\endgroup$ – Mr Puh Oct 14 at 8:01
  • $\begingroup$ What I was also hoping for is a mechanism that prevents me from setting parent scope variables in a local function, like a security option. Is there anything existing like this? $\endgroup$ – Mr Puh Oct 14 at 8:25
  • $\begingroup$ @MrPuh, Not that I am aware of. You might be lucky and there is some compiler warning, that you might be able to switch on, but I don't know that by heart, sorry. As for the newer Fortran, I think to remember that encapsulation got a lot better with the newer revisions. But the decision of moving to newer versions depends strongly on the size of your codebase and your task within it. $\endgroup$ – MPIchael Oct 14 at 12:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.