Fortran (2023)

This assessment applies the coupling account — coupling as access to a shared resource, and the coupling floor [1], [2] — and the knowledge-embodiment theorem [3]; the language is assessed against its official reference [4].

1 Assessment

This page assesses Fortran in five parts: core coupling — what the language’s own constructs force; ecosystem coupling — what the surrounding platform, tooling, and community cost; engineering readiness — whether the artifact can be reproduced, verified, observed, worked in quickly, connected to other languages, and governed openly; the profile — the unscored facts a score cannot express; and the scoring detail — the mechanism marks and the arithmetic behind every score. A ✅ bullet marks a construct that helps. A ⚠️ or ❌ bullet marks a cost; the scoring model does not price every cost named here.

Core coupling 32/100 · Ecosystem coupling 68/100 · Engineering readiness 65/100

1.1 Core coupling

1.1.1 Type

  • ⚠️ Typing is static but implicit typing is the default: Without implicit none, a name beginning with I–N is an integer and every other name is a real, so a misspelled variable becomes a new one rather than an error (T2)
  • ❌ There are no sum types: An alternative is encoded as an integer tag beside the payload fields, and select case or select type must decode it, so nothing checks that the right branch was taken (T4)
  • ⚠️ There are no generics, and a generic interface overloads a name instead: An interface block binds one name to several specific procedures and the compiler resolves the call by argument type, which is overloading without a type parameter (T5)
  • ⚠️ Operator and assignment overloading is expressible: interface operator(+) and interface assignment(=) redefine + and = for a derived type, so the work behind an operator is invisible at the call site (T8)
  • ✅ A module is a namespace with a selective import: use mymodule, only: a, b brings in two names and nothing else, so no flat global namespace is imposed
  • ✅ class(t) and select type give checked run-time polymorphism: A polymorphic dummy argument dispatches on the dynamic type of the actual argument, and select type narrows it in a checked branch

1.1.2 Construction

  • ❌ Global mutable state is the default storage class: A module variable and a common block are shared by every procedure that names them, and a variable marked save keeps its value between calls (C1)
  • ❌ There is no immutability by default: Only a parameter names a constant, and a module variable is writable by any user unless it carries the Fortran 2003 protected attribute (C2)
  • ❌ A structure constructor validates nothing: t(a, b) fills the components in order or by keyword, and no invariant can be stated at the type, so a valid object exists only because the caller made it valid (C4)
  • ✅ A pure procedure states the absence of side effects: It may not write a global, an intent(in) argument, or a saved variable, and the compiler enforces the rule
  • ✅ intent(in), intent(out), and intent(inout) state the direction of an argument: The declaration says whether a routine reads, writes, or does both, and the compiler checks it at the call site
  • ✅ elemental states an elementwise operation: An elemental procedure applies to each element of an array, so a scalar definition composes with array construction without a written loop

1.1.3 Memory

  • ❌ allocate and deallocate are written by hand for a pointer: The language tracks no owner and no bound, so a pointer target is freed by an explicit statement and a missing one leaks (M1)
  • ❌ There is no ownership model: A pointer may alias a target, dangle after deallocate, or be freed twice, and the compiler does not see the aliasing (M4)
  • ⚠️ Release tied to scope is partial: An allocatable local and a derived-type component with a final procedure are released at scope exit, but a pointer is not, so the rule differs by variable kind (M3)
  • ⚠️ An allocation failure must be tested: allocate(a(n), stat=ierr) returns a code the program has to check, and without stat= a failure aborts the program
  • ✅ move_alloc transfers an allocation without a copy: call move_alloc(from, to) moves the storage and deallocates the source, so a result array is returned without duplication
  • ✅ Allocatable locals are released at procedure exit: An allocatable array declared inside a procedure is deallocated when the procedure returns, which is a scope-tied release the language provides

1.1.4 Error

  • ❌ There is no exception mechanism: A failure is reported through a stat=/iostat= code or an err= label, and the caller has to test it (Er1)
  • ❌ A returned code can be ignored: Nothing obliges a caller to look at iostat, so a failed read, allocate, or deallocate proceeds as though it had succeeded unless the program checks (Er3)
  • ❌ There is no composable result type: No value carries either a result or an error, so error state travels in an extra out argument or in a saved flag (Er4)
  • ✅ error stop gives a defined abort: Since Fortran 2008, error stop stops the program with a message or an integer stop-code, and Fortran 2018 added the optional quiet= specifier, so a fatal failure is stated rather than inferred from a negative status
  • ⚠️ IEEE exceptional values are inspected through an intrinsic module: ieee_arithmetic and ieee_exceptions let a program test ieee_is_nan and select a halting mode, but the checks are ordinary calls rather than a language guarantee
  • ✅ A pure procedure cannot perform I/O: An I/O failure is outside a pure procedure by construction, so a computational kernel has no I/O error channel to handle

1.1.5 Concurrency

  • ❌ Coarray images share memory by default: A coarray declared with codimension is written by one image and read by another, and nothing in the language prevents the race (K2)
  • ⚠️ Synchronisation is manual: sync all, sync images, event post/event wait, and lock/unlock are image control statements the program must place at the right point
  • ✅ do concurrent states an independent loop: The construct asserts that the iterations have no interdependencies, and Fortran 2018 adds local, local_init, and shared locality specifications, so the compiler may parallelise it
  • ✅ atomic and critical give a defined atomic access: atomic define/atomic_ref and a critical construct express one update that must not interleave
  • ⚠️ The practical parallel model sits outside the language: Coarray support varies across compilers, so most high-performance code uses OpenMP directives or MPI calls

1.1.6 Platform

  • ✅ One standard source runs on every major compiler: gfortran, ifx, nagfor, Cray, and LLVM flang accept standard-conforming Fortran on Linux, Windows, macOS, and the large HPC systems
  • ✅ iso_c_binding names the interoperable interface: c_int, c_double, c_ptr, and c_funptr let a Fortran routine be called from C and call C in return
  • ✅ iso_fortran_env names the portable kinds and units: real64, int32, and input_unit state a portable choice where real(8) names one machine’s representation
  • ✅ A compiled module is a binary interface: A module is compiled to a file a consumer imports, so the interface is checked when the consumer is compiled
  • ⚠️ Vendor directives are common in working code: !DIR$ and !DEC$ directives, REAL*8, and STRUCTURE/RECORD appear in legacy HPC source, which binds it to one compiler
  • ⚠️ Coarray support varies by compiler and platform: A coarray program depends on the compiler’s image implementation and its transport library, so its portability is weaker than that of a serial program

1.1.7 Trade-offs (not coupling)

  • implicit none is the modern practice, not the standard default: A project chooses to require it, and the language permits implicit typing for the old source
  • Column-major order is a deliberate numerical choice: Array expressions follow Fortran’s storage order, which is what the linear-algebra kernels are written for
  • The aliasing rules are what the optimiser needs: The standard’s restrictions let the compiler vectorise a loop, which costs flexibility and buys speed
  • goto remains for compatibility: It is retained so that decades of code keeps compiling, and a new program need not use it
  • Whole-array syntax exists so the compiler chooses the loop nest: The programmer gives up per-loop control in exchange for vectorisation
  • The legacy corpus is working, tested numerical code: The aging ecosystem holds decades of validated physics and linear-algebra routines

1.2 Ecosystem coupling

1.2.1 Performance

  • ✅ The program is compiled to machine code: gfortran, ifx, and flang emit native objects for the target CPU, and the performance of an array kernel rivals C
  • ✅ Array syntax maps onto the machine’s loop nests: a = b + c over whole arrays lets the compiler fuse and vectorise the loops, and matmul and dot_product have library or instruction implementations
  • ✅ Nothing between the source and the CPU interprets: A Fortran binary has no bytecode dispatch and no JIT warm-up
  • ⚠️ The optimiser needs the aliasing facts: Without intent(in), contiguous, and pure, the compiler must assume a possible alias and cannot reorder the loops
  • ⚠️ The floating-point contract is the compiler’s flags: -O3 with -ffast-math or -fp-model fast changes the order of the operations, so the measured speed depends on the build options

1.2.2 Ecosystem

  • ❌ There is no standard package manager for the language: Dependencies are resolved by a language-agnostic manager such as Spack or Conda or by the system manager, and the fpm package manager is young and small (Ec3)
  • ✅ The numerical libraries are mature and standard: BLAS, LAPACK, and the vendor-tuned kernels (MKL, ESSL) are callable from Fortran directly
  • ✅ A compiler is available on every system: A Fortran compiler is installed on every HPC cluster and is packaged for every common Linux distribution
  • ✅ Interoperation with the C and Python ecosystems is standard: iso_c_binding and the Python wrapper generators make a Fortran kernel callable from a Python array library
  • ⚠️ Tooling beyond the compiler is thin: Formatting, lint, and documentation come from small community projects (fprettify, ford) rather than from a supported toolchain
  • ⚠️ The community is aging: Adoption is declining and maintenance is concentrated in the HPC and numerical community

1.2.3 Evolution

  • ✅ The standard is backward compatible: Fortran 90 source mostly compiles with a Fortran 2023 compiler, and only a small set of features has been deleted
  • ✅ The compilers retain the deleted features as extensions: arithmetic IF and PAUSE, both removed by the standard, are still accepted by the common compilers
  • ✅ The intrinsic modules extend the language without editing it: iso_c_binding, iso_fortran_env, and the parallel features were added as modules and new syntax rather than as changes to existing syntax
  • ⚠️ A revision takes about a decade: The editions are 2003, 2008, 2018, and 2023, so a needed feature waits years for the standard and lives as a compiler extension until then
  • ⚠️ One program can hold several standards: A single codebase can mix a Fortran 77 common block with a Fortran 2008 module, so the version in use is a property of the file
  • ⚠️ An extension can become load-bearing: A program that depends on a vendor directive cannot be moved to another compiler without an edit

1.2.4 Domain

  • ✅ Numerical and scientific computing is the design target: Array syntax, complex arithmetic, real kinds, and the IEEE intrinsic modules fit it directly
  • ✅ High-performance computing is the deepest adoption: Weather, climate, and physics codes are written in Fortran, so the language dominates legacy HPC codebases
  • ✅ submodule fits a large numerical codebase: A module can declare the interface and a submodule hold the implementation, so changing a kernel recompiles only that submodule (Fortran 2008)
  • ✅ Coarrays put a parallel program in the language: A distributed-memory program is written without a call from a separate parallel library
  • ⚠️ The language assumes a numerical, batch-shaped workload: Text handling, dynamic data structures, and event-driven logic get little support, and the standard library is mainly the intrinsic procedures
  • ⚠️ A new project often keeps Fortran for the kernel only: The language is used where the inner loop must be native, and the orchestration moves to Python or C++

1.2.5 Learning

  • ✅ The core is small once implicit none is in place: The statement forms, the intrinsic procedures, and the array expressions are a modest set
  • ✅ The authoritative material is stable and complete: The ISO standard, the compiler references, and Modern Fortran Explained cover the language in one place
  • ✅ The array idiom has a direct mathematical reading: A whole-array expression reads as the equation it computes
  • ❌ Legacy syntax quirks create friction: Fixed-form source with its column rules, implicit typing, and common blocks must be read to work on an existing codebase (L1)
  • ⚠️ The array and parallel idioms take practice: Array sections, where, and do concurrent read differently from an explicit loop, and the cost of a temporary array is easy to miss

1.2.6 Syntax

  • ❌ Fixed-form source is column-sensitive: In a .f file a label sits in columns 1–5, a continuation mark in column 6, and a statement starts in column 7, so the layout carries meaning
  • ⚠️ The language is verbose: Kind, intent, and dimension accumulate on every dummy argument, and the internals of a procedure need their own contains block (S1)
  • ✅ Whole-array expressions are compact: c = matmul(a, b) + alpha * c states a matrix update in one line
  • ✅ Free-form source is line-oriented and readable: With implicit none and use, only:, a modern module reads close to the mathematics
  • ⚠️ A common block’s storage is positional: Two declarations of the same block must list the fields in the same order with the same types, and the block’s layout is a convention rather than a checked interface

1.2.7 Testing

  • ✅ error stop gives a test program a defined failure status: A failing check stops the program with a non-zero status that a harness can read
  • ✅ Run-time checks can be compiled in: -fcheck=all (gfortran) and -check all (ifx) raise on an out-of-bounds subscript, an unassociated pointer, and a shape mismatch
  • ✅ The numerical libraries ship their own test suites: LAPACK and BLAS carry a published suite that exercises each routine, which is the numerical community’s model
  • ❌ There is no test framework in the language: A test is a program that runs and compares, and a runner such as pFUnit or test-drive is a third-party library
  • ⚠️ There is no assertion intrinsic: A check is an explicit if (cond) error stop, so its presence is a written decision
  • ⚠️ The result depends on the flags and the hardware: The summation order, -ffast-math, and the fused multiply-add change the last bits, so a test must fix the build and compare with a tolerance

1.2.8 Security

  • ⚠️ Memory safety depends on a checked build: An out-of-bounds subscript, an unassociated pointer, and a mismatch between an actual and a dummy argument are found only when the compiler is asked to check them, and the standard requires no run-time check
  • ✅ There is no dynamic evaluation of source text: The language has no eval, no reflective call by name, and no string-to-code path, so a value cannot become code
  • ✅ intent(in) prevents a write through the callee: The compiler refuses an assignment to an intent(in) dummy argument, so the read-only direction is enforced
  • ⚠️ The C interface hands safety to the neighbour: c_ptr and type(c_funptr) are raw addresses, and the guarantees of the C code on the other side become part of the program
  • ⚠️ There is no capability model and no sandbox: A program runs with the authority of the process, and the mitigations come from the operating system and the linker
  • ✅ A pure procedure cannot corrupt state through I/O or a global: The effect set of a pure procedure is empty by construction

1.3 Engineering readiness

1.3.1 Reproducibility

  • ❌ Builds are not byte-reproducible by default.
  • ❌ Dependencies resolve without a lock by default.

1.3.2 Supply chain

  • ⚠️ Published packages carry no signed provenance by default.

1.3.3 Observability

  • ⚠️ No stable tracing and metrics standard.
  • ⚠️ The standard library has no structured logging.

1.3.4 Feedback

  • ✅ No charged mechanism in this dimension.

1.3.5 Interop

  • ✅ No charged mechanism in this dimension.

1.3.6 Governance

  • ✅ No charged mechanism in this dimension.

1.4 Profile

Unscored, and kept separate from every score on purpose. The profile records what a mechanism price cannot express: what a running program costs, how well a coding model and an agent work in the language, and whether a safety-critical path exists.

1.4.1 Energy and cost

What this axis asks: what does a running program cost in energy and time?

  • ✅ Compiles to native code: the fastest class in the language energy and time benchmarks [5], [6]. A causal re-analysis attributes the difference to execution time [7].

1.4.2 AI and agent readiness

What this axis asks: how well does a coding model and an agent work in this language?

  • ❌ Sparse training data: models write it unreliably [8].

1.4.3 Safety certification

What this axis asks: is there a certified or standards-qualified path to a safety-critical system?

  • ⚠️ No certified or standards-qualified path for safety-critical systems.

1.5 Scoring detail

1.5.1 Core coupling Dimensions

Dimension Mechanism Description Present Cost
Type T1 dynamic no static check — 0
T2 weak static ✅ −8
T3 dynamic/duck dispatch — 0
T4 no sum types ✅ −4
T5 no generics ✅ −4
T6 no namespaces — 0
T7 type erasure/reflection — 0
T8 operator overloading ✅ −4
T9 inheritance required for polymorphism — 0
T10 no null-safety — 0
T11 no module boundary — 0
T12 no effect tracking ✅ −8
T13 async/sync colouring — 0
T14 unchecked foreign boundary ✅ −4
Type subtotal −15 (−32)
Construction C1 global mutable state by default ✅ −15
C2 no immutability by default ✅ −4
C3 constructors coupled to allocation — 0
C4 no constructor/validation ✅ −4
Construction subtotal −15 (−23)
Memory M1 manual deallocation ✅ −15
M2 GC non-deterministic pauses — 0
M3 no RAII ✅ −4
M4 no ownership model ✅ −8
Memory subtotal −15 (−27)
Error Er1 no structured path ✅ −15
Er2 unchecked exceptions only — 0
Er3 errors ignored/non-exhaustive ✅ −4
Er4 no composable result type ✅ −4
Error subtotal −15 (−23)
Concurrency K1 GIL/no parallel — 0
K2 shared mutable state default ✅ −8
K3 manual sync no race detection — 0
K4 no structured concurrency — 0
K5 no primitives — 0
Concurrency subtotal −8
Platform P1 cannot function outside one vendor/domain — 0
P2 single-vendor but other uses — 0
P3 legacy/discontinued platform — 0
Platform subtotal 0

Core Dimensions score: 32/100 — total cost = −68 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).

1.5.2 Ecosystem coupling Dimensions

Dimension Mechanism Description Present Cost
Performance R1 interpreted no AOT/JIT — 0
R2 GC pauses — 0
R3 JIT warmup — 0
Performance subtotal 0
Ecosystem Ec1 no package manager — 0
Ec2 fragmented tooling — 0
Ec3 small/niche/discontinued ✅ −8
Ec4 proprietary — 0
Ec5 platform-locked tooling — 0
Ec6 monoculture — 0
Ec7 legacy burden — 0
Ecosystem subtotal −8
Evolution V1 breaking changes — 0
V2 frozen — 0
V3 discontinued — 0
V4 glacial standards — 0
Evolution subtotal 0
Domain D1 domain/vendor lock-in — 0
D2 poor domain fit ✅ −8
D3 framework forces architecture — 0
Domain subtotal −8
Learning L1 steep ✅ −8
L2 large surface/multi-paradigm — 0
L3 sparse docs — 0
Learning subtotal −8
Syntax S1 verbose ✅ −8
S2 inconsistent — 0
S3 punctuation-heavy — 0
Syntax subtotal −8
Testing G1 weak tooling — 0
G2 poor debugging — 0
G3 runtime-only errors — 0
Testing subtotal 0
Security Q1 memory-unsafe — 0
Q2 dynamic/weak typing risk — 0
Q3 known vulnerability/injection — 0
Security subtotal 0

Meta Dimensions score: 68/100 — total cost = −32 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).

1.5.3 Engineering readiness Dimensions

Dimension Mechanism Description Present Cost
Reproducibility Y1 build not reproducible ✅ −8
Y2 dependencies not pinned ✅ −8
Reproducibility subtotal −15 (−16)
Supply chain Y3 no signed provenance ✅ −8
Y4 registry without namespacing — 0
Supply chain subtotal −8
Observability Y5 no stable tracing standard ✅ −8
Y6 no structured logging ✅ −4
Observability subtotal −12
Feedback Y7 slow build — 0
Y8 no fast in-editor analysis — 0
Feedback subtotal 0
Interop Y9 no foreign-function interface — 0
Y10 no portable target — 0
Interop subtotal 0
Governance Y11 single-vendor governance — 0
Y12 closed specification — 0
Governance subtotal 0

Readiness Dimensions score: 65/100 — total cost = −35 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).

Verdict: Fortran’s design produces a low core score, because the constructs that make the numerical work fast are the constructs that couple. Array expressions and pure procedures are real separations, and a module with a submodule separates an interface from its implementation. But implicit typing hides a misspelled variable, common blocks and module variables are global mutable state, a pointer is freed by hand with no owner, and an error is a code the caller may ignore. Main strengths: the fit to numerical computing is exact, array performance rivals C, and one standard source runs on every major compiler. Main weaknesses: the ecosystem is small and aging, the revision cycle is slow, and the syntax of a legacy file depends on column positions.


The formal development. The definitions, theorems, and proofs behind this page are published in:

All published papers

References

[1]
Y. Loth, “On the nature of coupling: All coupling as shared resource access.” Zenodo, Aug. 2026. doi: 10.5281/zenodo.21925122.
[2]
Y. Loth, “Coupling as a consequence of cohesion: Decomposition, the coupling floor, and domain-imposed coupling.” Zenodo, Aug. 2026. doi: 10.5281/zenodo.21925201.
[3]
Y. Loth, “On the nature of cohesion: Cohesion as correctness relative to a partitioning rule.” Zenodo, Jul. 2026. doi: 10.5281/zenodo.21703193.
[4]
ISO/IEC JTC 1/SC 22, “Information technology — programming languages — Fortran.” International Organization for Standardization; International Standard ISO/IEC 1539-1:2023 (Fortran 2023), Geneva, Switzerland, 2023.
[5]
R. Pereira et al., “Energy efficiency across programming languages: How do energy, time, and memory relate?” in Proceedings of the 10th ACM SIGPLAN international conference on software language engineering (SLE), 2017, pp. 256–267. doi: 10.1145/3136014.3136031.
[6]
R. Pereira et al., “Ranking programming languages by energy efficiency,” Science of Computer Programming, 2021, doi: 10.1016/j.scico.2021.102609.
[7]
N. van Kempen, H.-J. Kwon, D. T. Nguyen, and E. D. Berger, “It’s not easy being green: On the energy efficiency of programming languages,” in Proceedings of the 40th IEEE/ACM international conference on automated software engineering (ASE), 2025. doi: 10.1109/ASE63991.2025.00131.
[8]
S. Joel, J. J. Wu, and F. H. Fard, “A survey on LLM-based code generation for low-resource and domain-specific programming languages,” ACM Transactions on Software Engineering and Methodology, 2025, doi: 10.1145/3770084.