From c14a5afd5ad6aaafd76f83ceb0945a848a65b409 Mon Sep 17 00:00:00 2001 From: Cian Hughes Date: Tue, 31 Mar 2026 02:25:15 +0100 Subject: [PATCH] Finished fortran features post --- .../fortran_features_that_fascinate_me.md | 227 ++++++++++++++++++ devenv.lock | 59 +++-- devenv.yaml | 6 +- 3 files changed, 273 insertions(+), 19 deletions(-) create mode 100644 content/posts/fortran_features_that_fascinate_me.md diff --git a/content/posts/fortran_features_that_fascinate_me.md b/content/posts/fortran_features_that_fascinate_me.md new file mode 100644 index 0000000..03f246b --- /dev/null +++ b/content/posts/fortran_features_that_fascinate_me.md @@ -0,0 +1,227 @@ ++++ +date = '2025-02-17T00:51:14Z' +draft = true +title = 'Fortran features that fascinate me' ++++ + +Recently, I've been working on a package intended to add zero overhead monadic error handling into +Fortran. I mostly just wanted to see if it could be done, and apparently I enjoy doing silly +things in niche (at least nowadays) programming languages. Like most people who write code for +scientific number crunching I have a bit of a love/hate relationship with this language; it has so +many weird quirks, but it is also just *so easy* to write high performance code in. Even if you +don't like it, you definitely have to respect the fact that the first high level language worked +so well that it's still the goto language for supercomputing. Speaking of `goto`: writing this +package got me thinking about some of the weird, interesting features Fortran has had throughout +its life that you don't really see in most other languages. Wome of these features are really +interesting, and can really get you thinking about just how different programming can be from the +heavily C influenced style most modern languages have adopted. + +> I promise: that `goto` segue wasn't planned! It was a happy accident... + +## Structured Fortran + +Before a lot of these features can be explained I first need to explain "structured" fortran for +those who are unfamiliar with the language. Most modern Fortran code that you might have seen is +"free-form" code. Free-form Fortran code looks a lot more like a modern (albeit strange) +programming language. + +```f08 +program main + implicit none + + integer :: i, j + + do i = 1, 10 + j = quadratic(i) + write (*, *) "x:", i, "| y:", j + end do +contains + function quadratic(x) result(y) + integer, intent(in) :: x + integer :: y + y = 2 * x**2 + 4 * x - 2 + end function quadratic +end program main +``` + +However, a lot of these features existed earlier in the history of the language, in FORTRAN77 and +earlier versions. Structured Fortran was only introduced in Fortran90, all earlier versions of the +language were exclusively the "structured" form of the language. + +```f77 + PROGRAM MAIN + INTEGER I, J, QUADRA + + 10 DO 20 I = 1, 10 + J = QUADRA(I) + WRITE (*, *) "x:", I, "| y:", J + 20 CONTINUE + END + + INTEGER FUNCTION QUADRA(X) + INTEGER X + QUADRA = 2 * X**2 + 4 * X - 2 + RETURN + END +``` + +As you can see: the functionally identical code in structured Fortran has some unusual features. +Most of these features (such as the compulsory label gutter on the left of the code) are holdovers +from a time when the language was programmed on punch cards. + +![Fortran punch cards](https://upload.wikimedia.org/wikipedia/commons/f/f8/GfhR_%2823%29.jpg "A stack of fortran punch cards") + +Here, you'll know if code is structured or free-form Fortran because structured Fortran will be +written in block capitals (as is convention in the Fortran programming community). + +## Implicit types (and no, not in the way you're thinking) + +Long before we had type systems with static analyzers that could derive implicit types for +variables, Fortran also had implicit typing all the way back in 1957. Well, as long as you used +variable names that aligned with mathematical convention that is. Any variable name starting with +`I`, `J`, `K`, `L`, `M`, or `N` was assumed to be an `INTEGER` by the compiler, and **anything** +else was just assumed to be a `REAL`. Needless to say: this became a massive pain in the ass +whenever anyone tried to write anything more complicated than a direct translation of a +mathematical formula. Want to call the first measurement from your sensor `INITIAL` so you know +it's the initial starting value? Whoops, sorry! You just passed a `REAL` to a variable of type +`INTEGER` because `INITIAL` starts with `I`. Ever wondered why basically all Fortran programs +start with the line `implicit none`? This is why; to disable this feature. + +Still, though: let's be honest when you write `i` in any programming language we pretty much +always mean an integer. I'm not saying this was a good idea, im just saying that maybe if python +assumed all my `i` variables were integers we could optimise those painfully slow for loops a +bit... + +## Arithmetic IF + +With those basics out of the way: on to the really interesting things! The first of these (and +the one that I sometimes find myself wishing for in modern languages) is the "arithmetic if" +statement. In the earliest days of Fortran: the concept of an "if statement" as we know it today +wasn't quite as obvious as you might think. Most earlier experimental attempts to create assembler +preprocessors (which eventually evolved into programming languages) simply used combinations of +conditional line execution and jumps for control flow. Fortran introduced the first `IF`, but it +wasn't an `if` that most programmers today would recognize; it was the "arithmetic" `IF`. + +```f77 + PROGRAM MAIN + WRITE (*, *) "Enter a number: " + READ (*, *) I + IF (i - 10) 10, 20, 30 + 10 WRITE (*, *) "You entered a number less than 10." + GOTO 40 + 20 WRITE (*, *) "You entered the number 10." + GOTO 40 + 30 WRITE (*, *) "You entered a number greater than 10." + 40 CONTINUE + END +``` + +The arithmetic if allows you to branch in 3 directions depending on whether the result of an +expression is less than, equal to, or greater than 0. This may sound somewhat pointless, but is +actually extremely useful when writing functions calculating mathematical formulae (which makes +sense, it is the "FORmula TRANslator" after all!) and fuzzy logic. Following the lead of ALGOL60, +this particular language feature was eventually replaced with the standard boolean `if` in +FORTRAN IV, and an `if/else` block in FORTRAN77, eventually being phased out completely in the +Fortran 2018 standard. It's quite clear why it was phased out as it tended to create spaghetti +code, but it's a cool little feature that makes you wonder "what if" about parallel universes +where arithmetic if became the standard and control flow is based on Kleene logic instead of +Boolean logic. + + +## Enter the `ENTRY` keyword + +Continuing the trend of whacky control flow constructs, we have the `ENTRY` keyword. This keyword +allows you to create **multiple entrypoints** for a single function. (note: a `subroutine` is just +a `function` that has no return value) + +```f08 +program main + call primary() + call secondary() +end program main + +subroutine primary() + write(*, *) "Primary entrypoint" +entry secondary() + write(*, *) "Secondary entrypoint" +end subroutine primary +``` + +```txt +Primary entrypoint +Secondary entrypoint +Secondary entrypoint +``` + +You might ask: + +- When will I ever use this? +- Should I ever use this? +- Do we still need this? + +And the answer to every one of those questions is "no!". It's a feature that can appear useful at +first... until you realise it's easy to achieve the exact same thing in almost any language. + +```python +def primary() + print("Primary entrypoint") + secondary() + +def secondary() + print("Secondary entrypoint") +``` + +And as a bonus: using this approach allows us to control what values are passed through the +boundary between the two entrypoints while avoiding the undefined behaviour that occurs if you +try to use a variable defined in `primary` inside the body of `secondary`. This feature was +just a terrible idea that it's definitely a good thing that we don't do anymore. + +## Equivalence, aka: type punning on crack + +Before C's type casting, Fortran had a similar idea implemented the opposite way around. This +gives all the danger of type casting, but now with double the footguns! Where C allows us to treat +a variable as a different type via casting, and punning the `EQUIVALENCE` keyword in Fortran +allows us to cram two different variables into the exact same memory space. You may ask: how is +this different from type punning? And the answer is that the moment we put `EQUIVALENCE` +**anywhere** in our code we have now created silent type punning throughout the **entire** +codebase. In essence we have taken the classic type punning footgun, multiplied it by however +many times we have used any of the variables in our code, and pointed every single one square +at our foot. And all it takes is one a light touch to **one** of the triggers to set off **every +single footgun**! + +```f08 +program equivalence_footgun + implicit none + + integer :: secret_int + real :: harmless_float + real, dimension(5) :: data_array + + equivalence (secret_int, harmless_float, data_array(3)) + + data_array = [1.0, 2.0, 3.14159, 4.0, 5.0] + + write (*, *) "Harmless Float is: ", harmless_float + + secret_int = 987654321 + + write (*, *) "Float is now: ", harmless_float + write (*, *) "Array element 3 is now: ", data_array(3) +end program equivalence_footgun +``` + +```txt +Harmless Float is: 3.14159012 +Float is now: 1.69684563E-03 +Array element 3 is now: 1.69684563E-03 +``` + +## Conclusion + +Anyway, that's just a list of a few features that I find interesting (in good ways and bad ways) +from the various versions of Fortran throughout its history. It's mostly just been a ramble about +something that's been on my mind as I've been playing around with this package for adding monadic +errors to Fortran. If anyone randomly comes across this and is wondering how that weird side +project went you can have a look at the repo [https://github.com/Cian-H/formerr](here). It's a fun +little attempt to take a clean, modern language feature and retrofit it into a wonderful but messy +language that all our modern HPC languages owe their origins to. diff --git a/devenv.lock b/devenv.lock index 41989ce..d53c257 100644 --- a/devenv.lock +++ b/devenv.lock @@ -3,10 +3,11 @@ "devenv": { "locked": { "dir": "src/modules", - "lastModified": 1741068816, + "lastModified": 1774880920, + "narHash": "sha256-VPLBe2ES5agAANNZKDNaBQE/socnjBeEr2zloVcJHKk=", "owner": "cachix", "repo": "devenv", - "rev": "9f6da63c162ad86b6fb84edcbd8c447fdc411c3d", + "rev": "f333c713e692b687a9ed7cc38ea5738cac67d38a", "type": "github" }, "original": { @@ -19,14 +20,15 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1733328505, - "owner": "edolstra", + "lastModified": 1767039857, + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", + "owner": "NixOS", "repo": "flake-compat", - "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", "type": "github" }, "original": { - "owner": "edolstra", + "owner": "NixOS", "repo": "flake-compat", "type": "github" } @@ -40,10 +42,11 @@ ] }, "locked": { - "lastModified": 1740915799, + "lastModified": 1774861927, + "narHash": "sha256-FB1fbeJQjaTMI2JFAa0LNMaYXiShiYbJA6puGQC4xdg=", "owner": "cachix", "repo": "git-hooks.nix", - "rev": "42b1ba089d2034d910566bf6b40830af6b8ec732", + "rev": "9c4469b68b62e122c3b3d2ab0ed3caeb04ff1ac4", "type": "github" }, "original": { @@ -61,6 +64,7 @@ }, "locked": { "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", "owner": "hercules-ci", "repo": "gitignore.nix", "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", @@ -73,11 +77,15 @@ } }, "nixpkgs": { + "inputs": { + "nixpkgs-src": "nixpkgs-src" + }, "locked": { - "lastModified": 1733477122, + "lastModified": 1774287239, + "narHash": "sha256-W3krsWcDwYuA3gPWsFA24YAXxOFUL6iIlT6IknAoNSE=", "owner": "cachix", "repo": "devenv-nixpkgs", - "rev": "7bd9e84d0452f6d2e63b6e6da29fe73fac951857", + "rev": "fa7125ea7f1ae5430010a6e071f68375a39bd24c", "type": "github" }, "original": { @@ -87,12 +95,30 @@ "type": "github" } }, - "nixpkgs-unstable": { + "nixpkgs-src": { + "flake": false, "locked": { - "lastModified": 1741010256, + "lastModified": 1773840656, + "narHash": "sha256-9tpvMGFteZnd3gRQZFlRCohVpqooygFuy9yjuyRL2C0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ba487dbc9d04e0634c64e3b1f0d25839a0a68246", + "rev": "9cf7092bdd603554bd8b63c216e8943cf9b12512", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-unstable": { + "locked": { + "lastModified": 1774709303, + "narHash": "sha256-D3Q07BbIA2KnTcSXIqqu9P586uWxN74zNoCH3h2ESHg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "8110df5ad7abf5d4c0f6fb0f8f978390e77f9685", "type": "github" }, "original": { @@ -107,13 +133,10 @@ "devenv": "devenv", "git-hooks": "git-hooks", "nixpkgs": "nixpkgs", - "nixpkgs-unstable": "nixpkgs-unstable", - "pre-commit-hooks": [ - "git-hooks" - ] + "nixpkgs-unstable": "nixpkgs-unstable" } } }, "root": "root", "version": 7 -} +} \ No newline at end of file diff --git a/devenv.yaml b/devenv.yaml index 114c668..89bc199 100644 --- a/devenv.yaml +++ b/devenv.yaml @@ -1,5 +1,9 @@ -# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json inputs: + git-hooks: + url: github:cachix/git-hooks.nix + inputs: + nixpkgs: + follows: nixpkgs nixpkgs: url: github:cachix/devenv-nixpkgs/rolling nixpkgs-unstable: