Notes on a Fortran Conversion
“I don’t know what the language of the future will look like, but I know it will be called Fortran”.
So, I’ve got a simulation of a chemical plant (the Tennesse Eastmann process), a Reference Control Problem (i.e., a benchmark for process control I want to set some ML models on - it’s open loop unstable). I could use a Simulink model, but that requires learning a new toolset… and frankly that’s probably at least as hard as programming. Programming isn’t that hard!
So, here’s what I found.
Easy stuff
- comment delimiters are different (’!’ - and they use to be ‘C’).
- line continuation in fixed-form is by ‘&’ and it used to be ‘.’. This doesn’t appear to be documented anywhere, so you might be wondering about what .foo means.
Fun with Arrays
- For those who care, indexing starts from 1 (but is changeable on a per-array basis!). Recommendation: stop being hidebound and use the local convention
- Speaking of indexing, it’s a) column-major, b) closed by round instead of square brackets. This… bothers me a lot less in FORTRAN than it does in matlab, for some reason. Column-major is actually a pain, both for mental models and regex. Unless you’re used to it, then the opposite is a pain.
- Array notation is, however, a first-class citizen. Why do more language not have this?
- Array constructors are
(/ /)
in f95, the more conventional[ ]
in f2003.
Subs and Functions
- Are, as far as I can tell, ALWAYS pass-by-reference. That actually makes the syntax a darn sight simpler.
You don’t need
&mut<*&%"*()$3"$<T>>>!I%>
(looking at you, Rust). Or even*
. You don’t need it - it’s already a pointer. Why doesn’t every imperative language do that? - That said, functions and subroutines both mutate inpt. Personally, what I’d love for an ownership model for a language is there to be two paradigms: methods (unless ABSOlUTELY necessary) to mutate data, and preferably those should only return Error/Not-Error (ala Rust) or throw exceptions. functions, that NEVER mutate arguments. Rust fails hard here - and all that ugly syntax is to allow it to muddle through that.
Declare to dream
Idiomatic modern fortran separates type and declaration like so:
integer :: foo
Older fortran does not require the ::
. So, um, if you’re reviewing older code the declaration is subtly different for no obvious reason.
Strict variable name length formats in older fortran mean aggressive use of shorthand. In scientific code… this actually isn’t so much of a problem. If you can’t work out what “TKR” means after an hour, go read a textbook.
Dimensioning seems a bit weird, you have multiple ways.
integer, dimension(2,2) :: foo
integer :: foo(2,2)
Both seem valid.
GOTO fail
- do loops are structured with goto line numbers. This… honestly isn’t that bad, at least if the original author doesn’t want you dead. it can make exit conditions a might tricky, however.
The bigger stuff
- Most well known is that F77 has a fixed line length (80 chars?) with reserved words.
- You don’t get cmdline arguments until f2003. Even as more of a scripter than a programmer, ouch.
- f95 onwards still compiles a lot of earlier stuff. So, those dual semi-colons? STILL optional.
- Here’s a neat trick: declare an array, say integer :: idv(20), and you can then populate it with \idv = 1`. Yup, that works (you now have twenty 1s). Efficient but confusing.
- Booleans were added late on, and dissapointingly there’s no great convention for mapping to integer/one-hot encoding.
int(.true.)
fails. - Implicit typing. God, famously, is real (unless declared ‘int’). Real is single precision. F77 signifies double precision with, well, “double precision”. This still works in f2003, but
real(kind=8)
is more informative. You can also specify precision usingd
, so1.d-12
is the same as 1.e-12, but in double precision. This, however, means thatmin(100., foo(1))
will raise errors if foo(1) is double. You’ll see similar initialising arrays, with either compilation errors or weird runtime errors caused by precision loss and type inference. Relates to: - famously, fortran type infers based on the first letter of the variable.
implicit none
sorts this, but you need it in a lot of places unless you compile with (gfortran)-fimplicit-none
. So do that.
Recommendations
Use -fimplicit-none, -fdefault-real-8, and -Wconversion flags, and -std=f2003 or later with gfortran if you can.