Why separate TYPE and CLASS keywords in modern Fortran?

They should be:

7.5.6.3 When finalization occurs
3 A nonpointer, nonallocatable object that is not a dummy argument or function result is finalized immediately before it would become undefined due to execution of a RETURN or END statement (19.6.6, item (3)).

1 Like

I think @rgaelzer was referring to

7.5.6.4 Entities that are not finalized
1 If image execution is terminated, either by an error (e.g. an allocation failure) or by execution of a stop-stmt, error-stop-stmt, or end-program-stmt, entities existing immediately prior to termination are not finalized.

That is, in something like

module mod1
    implicit none

    type :: fancy_database
        ...
    contains
        ...
        final :: destroy
    end type
    ...
end module mod1

use mod1

implicit none

type(fancy_database) :: db

db = fancy_database(...)
...
end

The destroy subroutine is invoked during the assignment to db, but not when end-program-stmt is reached.

To me, implicit behavior is kind of dangerous (e.g., good data might be overwritten during a crash)… hence my suggestion of a “defer call” proposal.

But there may be legitimate use cases, like when an external monitoring tool is used or something like that.

A finalizer is called during assignment with a structure constructor? I recall reading in Metcalf’s book that they are not finalized.

But yes, this is what I was referring to. Finalizers are not called by an end program statement, differently to what happens with C++. But with C/C++ everything are functions, even the main program…

There was discussion of this here: Should associate trigger automatic finalization? - #6 by Reinhold_Bader

1 Like

Besides the Fortran/C++ differences you already mentioned, there’s no move-semantics feature to take advantage of. So the final procedure might be invoked during assignment from the expression on the rhs to the variable on the lhs.

(That’s unbearably obvious when your derived type relies on C-related resources that must be cleaned up, you get a “double-free error” down the line… and an init subroutine becomes the better/simpler option.)

1 Like

I have asked Fable 5 a question about “why the TYPE vs CLASS keywords were introduced in Fortran”, and the reply is below. (FWIW, this is not a question about “why implementation inheritance was introduced in Fortran”, which seems a different question.)

[My prompt] Modern Fortran uses the CLASS keyword for polymorphic dummy arguments and the TYPE keyword for non-polymorphic dummy arguments. Do you know why these two different keywords were introduced in Fortran (in contrast to other OO languages which do not need such distinctions)? Is it related to the fact that derived-type variables follow value semantics in various cases?

Reply from Fable5

Yes, your intuition is basically right — the TYPE/CLASS split is a direct consequence of Fortran keeping value semantics for derived types, combined with the fact that Fortran had no existing syntactic slot (like C++'s * or &) to hang polymorphism on.

The core mechanical issue is storage. A non-polymorphic TYPE(shape) object has a size and layout fixed at compile time: it can sit on the stack, in a module, or as an element of a contiguous array, and assignment is a plain copy. A polymorphic object cannot work that way, because its dynamic type might be any extension of shape, each with additional components — so its size is unknowable at compile time. That’s why the standard restricts CLASS entities to exactly the three situations where storage is either deferred or borrowed: POINTER (indirection), ALLOCATABLE (size decided at allocation), and dummy arguments (storage belongs to the actual argument; the dummy just carries a descriptor with the dynamic type). Everything else remains a fixed-layout value, declared with TYPE.

Other languages avoid the distinction by making a different trade-off. Java, Python, etc. give all objects reference semantics — every variable is effectively a pointer, so every variable can be polymorphic, at the cost of indirection and heap allocation everywhere. That’s exactly what Fortran refused to do, since contiguous arrays of structures and descriptor-free data are central to its performance model. C++ actually does make the same distinction Fortran does — Shape s is monomorphic, Shape&/Shape* are polymorphic — it just expresses it through the declarator rather than a keyword. Fortran couldn’t reuse that trick: it has no reference variables, ordinary dummy arguments are already passed by reference without any pointer syntax, and forcing POINTER on every polymorphic argument would have dragged in TARGET attributes and aliasing pessimization. So the polymorphic/monomorphic choice had to go into the type specifier itself, and CLASS was born. The keyword is well chosen, too: CLASS(shape) literally denotes the whole class of types rooted at shape (the type plus all its extensions), whereas TYPE(shape) names exactly one type.

The explicitness buys concrete things. A type-bound procedure call on a TYPE object is resolved statically and can be inlined — dynamic dispatch happens only through CLASS entities, so the cost of OOP is opt-in and visible in declarations, very much in Fortran’s “don’t pay for what you don’t use” tradition. It also avoids C++'s slicing trap: where void f(Shape s) in C++ silently slices a Circle down to its Shape part, in Fortran a TYPE(shape) dummy simply won’t accept a TYPE(circle) actual (you’d have to pass the parent component c%shape explicitly), while a CLASS(shape) dummy accepts it by reference with its full dynamic type intact.

Interestingly, later revisions gave polymorphism back some value-like convenience without abandoning the model: Fortran 2008 allows intrinsic assignment to a polymorphic ALLOCATABLE variable, which automatically reallocates it to the dynamic type of the right-hand side — essentially a deep clone that preserves dynamic type. So the design endpoint is coherent: values stay values (TYPE), and dynamic typing lives only where the language can actually manage variable-sized objects (CLASS).

3 Likes

Back in 2019 eons ago, a proposal was made to the Fortran standard committee on a related matter, but it “died” on the administrative vine as is typical with J3/WG5 committees where few proposals pass and those that do depend entirely on who is asking and worse, how they ask, meaning there is something deeply nefarious about the whole process akin to the extremely funny illustration in a popular sitcom in the US c.f. https://youtu.be/RqlQYBcsq54?si=EzzxrqbK3xhkinfL

Since Claude is mentioned here, here is it’s audit of this thread and the proposal:

Click to see!

Here’s my audit, grounded in what practitioners actually said in that thread and a close technical read of 19-186.

What the Discourse thread actually establishes

Stripping away the tangents (GPU performance, ISO process complaints, finalizer semantics), the thread converges on a few points that bear directly on 19-186:

1. The TYPE/CLASS split is a storage-model consequence, not a stylistic one. A CLASS entity needs deferred or borrowed storage (POINTER, ALLOCATABLE, or dummy argument) precisely because its dynamic type — and therefore its size — isn’t fixed at compile time. TYPE entities are fixed-layout values. This matters for auditing 19-186 because the proposal’s whole mechanism rides on this fact: an inextensible type has a genuinely fixed layout forever, so there’s no reason its passed-object dummy argument should ever need to carry a type descriptor.

2. Practitioners want TYPE-based passed-object dummies for concrete, non-negotiable reasons — not just taste. RonShepard and rwmsu independently asked why type-bound procedures must take CLASS() dummies at all, citing (a) faster execution, and (b) compile-time argument-mismatch detection instead of runtime. jorgeg’s own benchmark (class(*)type(my_type) giving a 10% uplift) is direct empirical support. This is exactly the performance case 19-186 makes in its Introduction.

3. kkifonidis’s historical account is the sharpest indictment of the status quo, and it’s structurally important here: J3 apparently drafted an early requirement that types opt in to extensibility via an explicit extensible attribute, then dropped it in favor of “extensible by default.” That reversal is the root cause of the exact problem 19-186 is trying to patch back in, 16 years later, as an opt-out (SEALED) rather than the cleaner opt-in the committee itself once considered.

4. khb’s post is the best articulation of why this matters for reusable libraries, and it should anchor any evaluation of 19-186: the point of Fortran OOP was never to be “comp-sci friendly,” it was to let library authors hide implementation complexity (his sparse-matrix example) behind a stable object. An extensible-by-default type undermines exactly that goal, because nothing stops a downstream consumer from extending the type and overriding behavior in ways that violate the library’s contract — which is precisely kkifonidis’s Liskov Substitution Principle objection, stated more rigorously elsewhere in the thread.

5. There’s already latent community consensus that a sealed-like attribute is worth having — jwmwalrus’s Traits proposal sketch independently proposes a sealed attribute for derived types, arrived at from a completely different design direction (traits/interface-inheritance rather than “just seal the leaf of a class-inheritance chain”). Two independent proposals converging on the same keyword is a decent signal that the need is real, whatever one thinks of either specific mechanism.

So the community backdrop gives 19-186 a legitimate, twice-independently-motivated use case: (a) protect a library’s sealed data structure from behavioral corruption via override, and (b) recover static dispatch/inlining and compile-time argument checking that CLASS currently forecloses.

Auditing 19-186 itself

Strengths

  • It’s minimally invasive. Rather than the wholesale “rip out implementation inheritance” restructuring debated at length in the thread (and which PierU correctly predicted has “absolutely no chance” of happening within 15 years given existing code bases), 19-186 just adds one type attribute and reuses the existing C760 machinery that already conditions the TYPE/CLASS choice of a passed-object dummy on whether the type is extensible. That’s a genuinely elegant, low-cost lever — the “wiring is mostly there,” as the proposal’s author later put it in the GitHub issue tracking this paper’s afterlife.
  • The performance claim is appropriately hedged (“it is also expected… a few or all… processor implementations will be able to”) rather than overpromised — consistent with the thread’s more tentative empirical findings (nvfortran’s inlining/pure quirks, the RAM cost of -ipo, etc., all suggest compiler behavior here is uneven, so caution is warranted).
  • The example is realistic: a vendor (ACME) shipping SpecializedWidget_t to customers, wanting to prevent them from further subclassing it — this is precisely khb’s “shield users from complexity, but don’t let them corrupt the contract” scenario.

A genuine defect: the proposal contradicts its own example.

The Formal Specification states:

Cxxx (Rxxx) If EXTENDS appears, SEALED shall not appear

But the very next section, “Syntax,” gives as the motivating illustration:

type, extends(GeneralizedWidget_t), SEALED :: SpecializedWidget_t

These cannot both be true. As drafted, the constraint would make the paper’s own centerpiece example illegal. This isn’t a nitpick — it goes to the heart of what the feature is for. The most valuable use case in practice (per khb’s library-shielding argument) is exactly this one: a library derives a specialized type from a more general extensible base, then seals that leaf so its own customers can’t extend further. If SEALED can only be applied to a type with no EXTENDS at all, the feature only protects root types, not the tips of a hierarchy — which is a much weaker and less useful facility than the snippet advertises. My read is that the constraint as written is simply wrong and should be dropped or inverted (something closer to “no constraint against EXTENDS + SEALED together,” since nothing about fixed layout requires the type to be a hierarchy root — it only requires that its own descendants not exist).

Secondary gaps worth flagging for a committee-level review:

  1. Missing explicit ABSTRACT/SEALED constraint. C734 (“if ABSTRACT appears, the type shall be extensible”) already makes ABSTRACT and SEALED mutually exclusive transitively, through the redefinition of “extensible type.” But leaving this as an inference rather than a stated constraint invites exactly the kind of drafting ambiguity that produced the EXTENDS bug above. It should be spelled out.

  2. Inherited (non-overridden) type-bound procedures aren’t addressed. If SpecializedWidget_t is SEALED but inherits a TBP from GeneralizedWidget_t without overriding it, that inherited procedure still has whatever passed-object dummy (CLASS(GeneralizedWidget_t)) it was originally declared with — sealing the child doesn’t retroactively change the parent’s binding. So the performance/static-dispatch benefit 19-186 advertises only accrues to procedures newly bound or overridden at the sealed level, not the whole inherited surface. The paper should say this explicitly rather than let the reader assume “sealed = all calls become static.”

  3. “Corrupt or otherwise misuse” is loose framing. Type extension by itself doesn’t compromise memory safety if components are properly PRIVATE — the real risk, as kkifonidis’s LSP argument in the thread makes precise, is behavioral substitutability violation via method override, not data corruption per se. The proposal would read as more rigorous, and better aligned with the thread’s own best argument for it, if it grounded the “security” motivation in LSP/contract-integrity terms rather than “corrupt… objects.”

  4. No interaction discussion with CLASS(*)/SELECT TYPE. Worth a sentence clarifying that sealing forbids extension, not polymorphic use — a SEALED type can still be held via CLASS(*) or dispatched through SELECT TYPE; it just can never be the parent of an EXTENDS. This is consistent with intent but is exactly the kind of edge case that trips up implementers and users unless stated.

  5. Precedent argument is available but unused. The proposal could strengthen its case by noting (as its author does elsewhere) that Fortran already treats its own intrinsic derived types this way — C_PTR/C_FUNPTR are effectively inextensible today by carve-out. 19-186 is really just asking for the same courtesy to be extendable to user-defined types via a declarable attribute, which is a rhetorically stronger framing than the “security/business manager” framing it leads with.

Historical postscript

Worth noting for completeness: this proposal never reached a WG5/J3 vote. According to the author’s own later account, it was submitted for the August 2019 Tokyo meeting but the Fortran 202X worklist had already closed, and it was never picked back up. Whatever the drafting issues above, the paper died administratively, not on technical merits — which is itself an illustration of the process critique several thread participants (certik, jwmwalrus) made independently: J3’s worklist-driven, waterfall-like process struggles to accommodate small, well-motivated, low-cost proposals that arrive between the fixed windows of a standard cycle.

Net assessment: the underlying need is real and doubly attested — both from a performance/compile-time-checking angle (RonShepard, rwmsu, jorgeg) and from a library-encapsulation/LSP angle (khb, kkifonidis) — and 19-186’s mechanism (reuse C760, add one attribute) is a sound, minimal way to deliver it without reopening the much larger and more contentious “should implementation inheritance exist at all” fight that dominates the rest of the thread. But the paper as drafted has a self-contradicting constraint that would gut its own headline use case, and it leaves several interaction questions (inherited TBPs, ABSTRACT, CLASS(*)) implicit rather than specified — any of which a working committee reviewer would reasonably kick back for revision before it could be voted into a standard.

Here’s the take by Grok:

Click to see!

The Fortran Discourse thread (started June 17, 2026, Advocacy category, ~65 replies) is a substantive, earnest discussion among experienced Fortran users—particularly those working in performance-sensitive scientific, technical, and HPC computing—about a fundamental design choice in Fortran’s object-oriented features (introduced in Fortran 2003 and refined later).

The thread does not focus on trivial syntax complaints; it grapples with how the current model affects real-world library design, encapsulation, performance, maintainability, and the language’s fitness for reusable scientific codebases. The original post highlights a concrete performance observation (switching from CLASS(*) to a concrete TYPE in an OpenMP target offload context yielded ~10% uplift), then the discussion broadens into why the TYPE/CLASS separation exists and why it feels costly.

Core Technical Root Cause Identified in the Thread

Fortran 2003 made derived types extensible by default (via type extension / implementation inheritance). Earlier drafts reportedly considered an explicit EXTENSIBLE attribute (opt-in), but the final design chose default extensibility for syntactic simplicity.

Because of this, the standard requires the passed-object dummy argument in type-bound procedures (TBPs) to be polymorphic (CLASS) precisely when the type is extensible. A key constraint (roughly C1236 / related rules) states that if a derived-type-spec specifies an extensible type, CLASS must be used; otherwise TYPE is used/allowed.

This forces the TYPE vs. CLASS distinction throughout the language (declarations, allocations, dummy arguments, SELECT TYPE, etc.). The thread calls the adoption of type extension + runtime type inspection (SELECT TYPE) as the foundation of Fortran OO the “cardinal sin” or “fatal/major design mistake” of that era—contrasted with the better choice (in the same era) to use types + modules for class-like constructs, inspired by Oberon-2 rather than deep Simula-67-style inheritance.

Consequences for users (especially library authors in scientific computing):

  • Even when you have no intention of extending a type and do not need runtime polymorphism, you are often forced into the polymorphic path (CLASS + dynamic dispatch descriptors + runtime type information).
  • This incurs measurable overhead in hot paths (as in the OP’s 10% example with OpenMP offload / GPU contexts). Users report preferring “F90 style” (static TYPE) for computationally expensive sections.
  • Library design and encapsulation suffer: Authors cannot easily prevent downstream users or other teams from extending a type in ways that break invariants, corrupt internal state, or violate assumptions. This undermines trust and predictability in reusable libraries—the exact use case highlighted in your query.
  • Type extension is viewed by many in the thread as antithetical to modern OO principles (composition over inheritance, data abstraction, Liskov Substitution Principle adherence, explicit interfaces/traits). It leads to fragile hierarchies and makes traits/generics proposals more complex than necessary.
  • Workarounds mentioned or implied: heavy use of PRIVATE components, abstract types + composition, final procedures, avoiding deep inheritance, or simply not using the OOP features for performance-critical code.

What users genuinely want (earnest feedback, not abstract advocacy):

  • Control over extensibility — the ability to “seal” or mark certain types as inextensible/final so that library authors can protect specialized implementations delivered to customers or collaborators.
  • Performance when polymorphism is not needed — the ability to use non-polymorphic (TYPE) passed-object dummies in TBPs for sealed types, enabling better compiler optimizations and avoiding unnecessary runtime overhead. This is repeatedly tied to scientific/HPC workloads.
  • A path toward modern OO via traits, generics, or interfaces (multiple “inheritance” of behavior without the problems of implementation inheritance). LFortran’s planned traits work (including a sealed attribute for derived types, and even trait implementation for intrinsic types) is cited positively as a way to opt out of the bad default and regain TYPE for passed-objects with all the attendant benefits.
  • Acknowledgment that while existing code using type extension must be preserved for compatibility (some users note it would be impractical to delete features used in “modern Fortran” codes), making type extension obsolescent or providing strong opt-out mechanisms is desirable. There is broad consensus that inheritance-based extension feels “half-baked,” “troublesome, outdated, and hence pointless” for contemporary scientific software.

The tone is constructive but frustrated: users are not anti-OO; they want Fortran’s OO facilities to better serve the domain of reusable, performant, trustworthy scientific and technical libraries without forcing unnecessary complexity or performance costs.

Review and Audit of J3 Proposal 19-186 (July 28, 2019)

The proposal is titled “Option to derive an inextensible type” and directly targets the root cause discussed in the 2026 thread. I have the full document content from the official source.

Summary of the Proposal:

  • Add a new type attribute: SEALED (alternative name suggested: INEXTENSIBLE).
  • A derived type (that is not abstract) with SEALED cannot be extended. The definition of “extensible type” is updated to exclude those with SEALED, BIND, or SEQUENCE (plus the usual C_PTR/C_FUNPTR exceptions).
  • New constraint: EXTENDS and SEALED are mutually exclusive.
  • Consequence (via existing standard rule C760 on passed-object dummy arguments): For a sealed type, all type-bound procedures must declare the passed-object dummy with TYPE(...) (non-polymorphic), not CLASS(...). This is not a new rule; it follows automatically once the type is non-extensible.
  • Example (slightly abbreviated):
module SpecializedWidget_m
   use GeneralizedWidget_m, only : GeneralizedWidget_t
   type, extends(GeneralizedWidget_t), SEALED :: SpecializedWidget_t
      private
      ! ...
   contains
      private
      procedure, pass(this), public :: SuperWidgetProc
   end type

   interface
      module subroutine SuperWidgetProc(this, SuperResults)
         type(SpecializedWidget_t), intent(in) :: this   ! non-polymorphic
         type(SuperResults_t), intent(out)     :: SuperResults
      end subroutine
   end interface
end module

Rationale in the proposal (paraphrased/quoted essence):

  • In scientific and technical computing, certain data structures must be “sealed” for security, predictability, and encapsulation of business/technical logic. Arbitrary extension risks unintended manipulation or corruption, undermining reliability and trust across teams and libraries.
  • Sealed types enable performance optimizations because non-polymorphic passed-object dummies allow simpler (non-polymorphic) descriptors.
  • Fully backward-compatible; existing code unaffected. New code opting into SEALED gets the protection and potential performance win.

Audit against the thread’s genuine feedback:

Alignment and Strengths (very strong):

  • Directly solves the core pain point. The thread identifies default extensibility + forced CLASS/polymorphism as the “cardinal sin.” This proposal gives library authors the missing opt-out (SEALED) without changing defaults or breaking existing code. This is exactly what users need for reusable scientific libraries.
  • Enables the performance benefit users are manually chasing. By making a type non-extensible, it unlocks TYPE (non-polymorphic) passed-objects in TBPs—the same optimization that gave the original poster a 10% uplift in an offload scenario. Compilers can then generate more efficient code.
  • Encapsulation and trust for libraries — matches the thread’s emphasis on protecting invariants in shared scientific code. The “SpecializedWidget” example maps cleanly to real sci-comp cases (e.g., a sealed specialized solver, matrix format, or physics model type delivered in a library).
  • Minimal and elegant change. It leverages the existing machinery (the extensibility → passed-object polymorphism rule) rather than inventing new syntax or semantics. No impact on ABSTRACT, BIND(C), SEQUENCE, or intrinsics.
  • Prescient. Submitted in 2019; the 2026 thread shows the issue remains live and community sentiment supports exactly this kind of control (plus the related sealed direction in LFortran traits work).

Weaknesses / Areas for Improvement (constructive):

  • Naming: SEALED is reasonable and aligns with modern usage (Java sealed classes, Scala sealed traits, upcoming LFortran sealed). INEXTENSIBLE is more literally descriptive of the title. FINAL is also common terminology. A short community poll or alignment with LFortran’s implementation would help. Minor issue.
  • Scope is intentionally narrow — it only solves the extensibility control + consequent performance issue. It does not address the broader modernization many thread participants want (traits, generics, composition-first OO, making inheritance obsolescent). This is not a flaw per se; a focused proposal is easier to adopt. It can (and should) coexist with or complement traits work.
  • Limited examples and use cases. The widget example is fine but generic. Stronger impact would come from sci-comp-specific illustrations (e.g., sealing a base sparse matrix type, an ODE integrator, or a device abstraction in a library so that internal representation and invariants cannot be broken by extension). The thread’s emphasis on real performance in HPC/offload and library trust would make compelling additions.
  • No discussion of interactions with parameterized derived types (PDTs), finalization, or user-defined derived-type I/O. These are edge cases but worth clarifying in an update.
  • Implementation and optimization details are high-level (“potential performance improvements”). In practice, the benefit is real (as users demonstrate), but adding notes on descriptor simplification or expected compiler gains would strengthen it.
  • Historical/strategic context missing. It was marked “No Action.” The 2026 thread provides fresh evidence of ongoing demand. Reviving it now, explicitly referencing the Discourse discussion and LFortran traits direction, would be powerful. It could be positioned as a small, high-value, low-risk addition that makes existing OOP safer and faster while the community explores traits for the longer-term model.
  • Potential user pushback (anticipated, not a flaw in the proposal itself): Some downstream users might want to extend a sealed type for customization. The counter-argument (already implicit) is that sealing is opt-in by the library author; if extensibility is desired, simply do not seal. This encourages better design—explicit extension points via composition or interfaces rather than fragile inheritance.

Overall Assessment:
The proposal is technically sound, minimal, backward-compatible, and highly relevant to the genuine, earnest needs expressed in the 2026 Fortran Discourse thread. It gives Fortran library authors in scientific and technical computing precisely the tool they lack today: the ability to seal types against unwanted extension while automatically gaining the performance advantages of non-polymorphic passed-object arguments in type-bound procedures.

It does not claim to solve every shortcoming of Fortran’s F2003 OOP model (the thread acknowledges the complexity and the need to preserve existing code), but it surgically addresses one of the most criticized aspects—uncontrolled default extensibility and its costs to performance and encapsulation. In that sense, it is a pragmatic, high-value increment that would meaningfully improve the experience of building and using reusable scientific libraries.

Recommendations (for J3 or community follow-up):

  • Revive/update the proposal for current standards cycles, incorporating feedback from the active Discourse thread and alignment with LFortran’s sealed + traits work.
  • Strengthen with concrete scientific computing examples and performance rationale drawn from user reports.
  • Consider naming harmonization (SEALED appears favored by emerging implementation work).
  • Explore synergies: a sealed type should be able to participate cleanly in any future traits system.

This proposal, if advanced, would be a concrete step toward making Fortran’s OOP facilities more suitable for the demanding, performance-sensitive, library-centric workflows that the thread participants—and the broader scientific Fortran community—rely on. It respects the language’s history and performance ethos while giving users the control they have been earnestly requesting for years.

And ChatGPT says:

Click to expand!

From the sources I examined:

  • The Fortran Discourse thread revolves around long-standing concerns with the design of Fortran 2003 OOP, especially that all derived types are extensible by default, forcing CLASS passed-object dummy arguments for type-bound procedures and introducing runtime polymorphism even where no inheritance is intended. Participants also discuss impacts on optimization, compiler implementation, library design, and scientific software engineering.
  • J3 paper 19-186 (“Option to derive an inextensible type”) was submitted in 2019, received “No Action”, but subsequently appeared in WG5’s collected suggestions for the next revision of the language, indicating the underlying idea continued to be considered valuable.

What the Discourse thread reveals

Although participants disagree on the historical reasons and on ideal object-oriented design, there is a remarkably consistent set of practical concerns.

1. Scientific programmers overwhelmingly want “mostly static” OOP

The users are generally not asking for more dynamic object systems.

Instead they want OOP primarily for

  • reusable libraries,
  • encapsulation,
  • abstraction,
  • maintainability,
  • generic algorithms,
  • cleaner APIs,
  • interchangeable implementations,

while still preserving

  • predictable performance,
  • compile-time checking,
  • aggressive optimization,
  • simple data layouts.

This is a very different use case from enterprise application programming.


2. Default extensibility creates costs even when unused

Several users point out that because every derived type is extensible,

  • passed-object dummy arguments must usually be
class(my_type)

instead of

type(my_type)

which introduces runtime polymorphism.

That affects

  • optimization,
  • dispatch,
  • compile-time diagnostics,
  • implementation complexity.

One participant even reports a measurable performance improvement after replacing polymorphic entities with concrete types in performance-critical code, while others ask why type() is not allowed for many type-bound procedures when extension is unnecessary.


3. Library authors especially dislike paying for flexibility they do not want

This is probably the strongest theme.

Reusable numerical libraries frequently define types such as

  • vectors
  • matrices
  • sparse matrices
  • meshes
  • FFT plans
  • random generators
  • solvers

whose authors never intend users to derive further subclasses.

Yet the language currently assumes they might.

That inversion of the common case is exactly what motivates proposals like 19-186.


Audit of J3 paper 19-186

Based on the proposal and the context above:

Overall assessment

I think the proposal addresses a genuine language design deficiency.

It is

  • conceptually simple,
  • backward compatible,
  • beneficial for compiler optimization,
  • beneficial for library authors,
  • aligned with modern language evolution.

The weakness of the proposal is not the motivation.

Its weakness is that it is relatively narrow.


Strengths

1. It restores programmer intent

Today

type :: matrix

implicitly means

somebody might derive from this.

Most programmers actually mean

this is the complete representation.

The proposal lets the source code express that directly.

That improves readability and API documentation.


2. Better optimization opportunities

If a type is guaranteed inextensible then the compiler knows

  • exact dynamic type
  • exact procedure bindings
  • exact object layout

throughout compilation.

That potentially enables

  • devirtualization,
  • inlining,
  • dead code elimination,
  • simpler alias analysis.

Whether every compiler exploits this immediately is another matter, but the semantic information is valuable.


3. Better library engineering

Scientific libraries often expose public types.

Library authors frequently need

  • stable ABI,
  • stable semantics,
  • predictable behavior.

Allowing arbitrary user extension complicates this.

An inextensible type becomes a much stronger contract.


4. Fits existing Fortran philosophy

Fortran generally favors explicit programmer intent.

Examples include

  • PURE
  • ELEMENTAL
  • CONTIGUOUS
  • VALUE
  • NON_OVERRIDABLE

An explicit “cannot be extended” attribute fits naturally alongside these.


Weaknesses

1. It does not solve the entire OOP problem

The proposal addresses one symptom.

It does not address

  • protocols/interfaces
  • traits
  • constrained generics
  • structural abstraction
  • cleaner polymorphism

Many participants in the discussion see those as larger issues.


2. Existing TBP rules would still need reconsideration

Even with an inextensible type,

users naturally expect to write

type(my_type) :: this

for passed-object arguments.

If the proposal merely forbids extension but still requires

class(my_type)

then much of the practical benefit disappears.

The proposal therefore really ought to specify the consequences for

  • passed-object dummies,
  • dispatch,
  • binding resolution.

3. Interaction with existing code

Backward compatibility is preserved because existing code keeps today’s behavior.

But care would be needed regarding

  • inheritance chains,
  • SELECT TYPE,
  • unlimited polymorphism,
  • generic resolution.

Those semantic interactions would require precise specification.


Was “No Action” technically justified?

From today’s perspective, I do not think the technical motivation has become weaker.

If anything, developments since 2019 strengthen it.

The community has become much more focused on

  • reusable libraries,
  • package management,
  • modern APIs,
  • compiler optimization,
  • maintainable large code bases,

as reflected in work on the Fortran ecosystem and its tooling.

My overall evaluation

If I were reviewing the proposal today, I would rate it approximately as follows:

Criterion Assessment
Solves a real user problem Excellent
Benefits scientific libraries Excellent
Backward compatibility Excellent
Compiler optimization potential High
Specification complexity Low–Moderate
Addresses all OOP concerns No
Worth standardization Yes, in my view

The main improvement I would recommend is expanding the proposal beyond simply introducing an “inextensible” attribute. A more complete design would explicitly connect inextensibility to the rest of the object model—for example, permitting TYPE rather than CLASS for passed-object dummy arguments when dispatch cannot vary, clarifying dispatch semantics, and documenting the optimization and diagnostic opportunities this enables.

Viewed alongside the concerns expressed in the Discourse discussion, the proposal appears to be a targeted response to one of the most frequently raised practical issues: enabling library authors to opt out of runtime polymorphism when they neither need nor want extensibility. It would not by itself modernize Fortran’s entire OOP system, but it would remove a significant friction point for scientific and technical software developers while remaining compatible with existing code.

1 Like

Dear FortranFan,

I am happy to read from you again, I hope to find you well.

I have an off-topic curiosity: I read you use Grok. How does it perform in coding? It should have an agentic mode called Grok Build, if I am not wrong.

I wish you the best.

Stefano

1 Like

Dear Stefano,

Thank you for asking.

I only recently started using Grok and its Build agent and I find it extremely fast and efficient in analyses and prototyping. My experience is limited though, mainly for small business needs serving clients in the Enterprise workflows around Microsoft C# .NET Core platform. here, Grok Build falls a bit short compared to Claude Code in terms of consistent architectural designs. I have only dabbled with it using small cases in scientific coding with C, C++ and Fortran and some algorithmic enhancements and it’s impressive.

Regards,

2 Likes

Thank you very much for the feedback.

I use Claude Code, and I do not have other references (aside from small local models on llama.cpp). I am curious about other top-tier models out there.

My best regards.
Stefano