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 anintegerand every other name is areal, 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 caseorselect typemust 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
interfaceblock 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(+)andinterface 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, bbrings in two names and nothing else, so no flat global namespace is imposed - ✅
class(t)andselect typegive checked run-time polymorphism: A polymorphic dummy argument dispatches on the dynamic type of the actual argument, andselect typenarrows it in a checked branch
1.1.2 Construction
- ❌ Global mutable state is the default storage class: A module variable and a
commonblock are shared by every procedure that names them, and a variable markedsavekeeps its value between calls (C1) - ❌ There is no immutability by default: Only a
parameternames a constant, and a module variable is writable by any user unless it carries the Fortran 2003protectedattribute (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
pureprocedure states the absence of side effects: It may not write a global, anintent(in)argument, or asaved variable, and the compiler enforces the rule - ✅
intent(in),intent(out), andintent(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 - ✅
elementalstates an elementwise operation: Anelementalprocedure applies to each element of an array, so a scalar definition composes with array construction without a written loop
1.1.3 Memory
- ❌
allocateanddeallocateare written by hand for apointer: 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
pointermay alias atarget, dangle afterdeallocate, 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
finalprocedure are released at scope exit, but apointeris 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 withoutstat=a failure aborts the program - ✅
move_alloctransfers 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 anerr=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 failedread,allocate, ordeallocateproceeds 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 stopgives a defined abort: Since Fortran 2008,error stopstops the program with a message or an integer stop-code, and Fortran 2018 added the optionalquiet=specifier, so a fatal failure is stated rather than inferred from a negative status - ⚠️ IEEE exceptional values are inspected through an intrinsic module:
ieee_arithmeticandieee_exceptionslet a program testieee_is_nanand select a halting mode, but the checks are ordinary calls rather than a language guarantee - ✅ A
pureprocedure cannot perform I/O: An I/O failure is outside apureprocedure 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
codimensionis 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, andlock/unlockare image control statements the program must place at the right point - ✅
do concurrentstates an independent loop: The construct asserts that the iterations have no interdependencies, and Fortran 2018 addslocal,local_init, andsharedlocality specifications, so the compiler may parallelise it - ✅
atomicandcriticalgive a defined atomic access:atomic define/atomic_refand acriticalconstruct 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 LLVMflangaccept standard-conforming Fortran on Linux, Windows, macOS, and the large HPC systems - ✅
iso_c_bindingnames the interoperable interface:c_int,c_double,c_ptr, andc_funptrlet a Fortran routine be called from C and call C in return - ✅
iso_fortran_envnames the portable kinds and units:real64,int32, andinput_unitstate a portable choice wherereal(8)names one machine’s representation - ✅ A compiled module is a binary interface: A
moduleis 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, andSTRUCTURE/RECORDappear 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 noneis 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
gotoremains 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, andflangemit 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 + cover whole arrays lets the compiler fuse and vectorise the loops, andmatmulanddot_producthave 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, andpure, the compiler must assume a possible alias and cannot reorder the loops - ⚠️ The floating-point contract is the compiler’s flags:
-O3with-ffast-mathor-fp-model fastchanges 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
fpmpackage 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_bindingand 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 IFandPAUSE, 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
commonblock 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
- ✅
submodulefits a large numerical codebase: A module can declare the interface and asubmodulehold 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 noneis 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
commonblocks must be read to work on an existing codebase (L1) - ⚠️ The array and parallel idioms take practice: Array sections,
where, anddo concurrentread 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
.ffile 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 owncontainsblock (S1) - ✅ Whole-array expressions are compact:
c = matmul(a, b) + alpha * cstates a matrix update in one line - ✅ Free-form source is line-oriented and readable: With
implicit noneanduse, only:, a modern module reads close to the mathematics - ⚠️ A
commonblock’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 stopgives 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
pFUnitortest-driveis 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 anintent(in)dummy argument, so the read-only direction is enforced - ⚠️ The C interface hands safety to the neighbour:
c_ptrandtype(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
pureprocedure cannot corrupt state through I/O or a global: The effect set of apureprocedure 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?
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:
- On the Nature of Coupling: All Coupling as Shared Resource Access — Zenodo, 2026 — doi:10.5281/zenodo.21925122
- Coupling as a Consequence of Cohesion: Decomposition, the Coupling Floor, and Domain-Imposed Coupling — Zenodo, 2026 — doi:10.5281/zenodo.21925201
- On the Nature of Cohesion: Cohesion as Correctness Relative to a Partitioning Rule — Zenodo, 2026 — doi:10.5281/zenodo.21703193