I thought folks here would be interested in this article about how hard it has been for governments and businesses to transition away from COBOL. Unlike Fortran, which is almost as old as COBOL, programmers are still learning Fortran and the language standard has continued to evolve and stay relevant.
I recently made a list of some of the “asbestos” that has accumulated in Fortran over the last 20 years. I thought this list might be of interest also to others, who’d like to avoid “exposure” (in order to live a more healthy programming life). So here it is:
-
Implementation inheritance (type-extension). Classification: Highly toxic! “Asbestos” that dates back to the 1960ies. Was long known to be obsolescent in Objective-C and Java, when it was introduced into Fortran 2003 (published in 2005). Modern alternative/antidote: Interface inheritance/Traits.
-
Procedure pointers. Classification: Hazardous! Moreover superfluous in languages that support dynamic dispatch (thus obsolescent since the 1960ies). Nevertheless introduced into Fortran 2003. Antidote: Closures.
-
Parameterized Derived Types. Classification: Unhealthy (they cause flatulence, like all half-baked things)! Not interoperable with the language’s object-oriented features. Made it into Fortran 2008, regardless. Antidote: Generic derived types that conform to Traits.
-
Submodules. Classification: Superfluous (in a language that supports object-orientation, OO). Submodules do not allow for full code decoupling as OO does, while adding tremendous complexity. Deleted by Niklaus Wirth already in 1986, when he went from Modula-2 to its object-oriented successor Oberon. Needless to say they, too, made it into Fortran (2008). Antidote: Modules + OO.
There’s a saying in Germany: “Wer im Glashaus sitzt sollte nicht mit Steinen werfen.” Given all these existential problems in our own yard, maybe we should not be laughing at COBOL.
There is an old saying in my part of the world about being “A day late and a dollar short”. Unfortunately, this sort of describes how some see Fortran. I’ve always been puzzled by why inheritance was implemented the way it is in Fortran when as you suggest other languages (I think influenced by the Design Pattern research) had already begun to move away from it when the Fortran OOP facility was being designed. My take on PDTs has always been a good idea poorly implemented.
It was proposed in Fortran 2003 and I think NAG added it in 2006, Intel 2009, GFortran around 2010.
For comparison, C++ compilers supported that around 1985, and the standard added it in 1998.
Newer languages like Rust (2006) and Go (2007) deliberately did not include inheritance, because it was well-known already back then this is not the way. Instead I think all modern languages use interfaces/traits.
The lesson to learn here is to always implement major features in compilers first, and only then standardize, not the other way round. And to learn from other languages.
I don’t know about Rust, but Go’s “embedding and promotion” behaves just like Fortran’s inheritance; you can access components and methods directly or through the type name. Also, Go’s embedding sort of resembles multiple inheritance, even with the inherent diamond problem (which Go solves by throwing a compilation error, but only if the conflicting item is ever accessed… just like Fortran’s USE of conflicting references?).
Or, at least, switch to each standard being a compendium of technical reports, so compiler vendors can implement them as they come, instead of waiting 5 years for publication… and another 5-10 implementing the whole thing.
(e.g., in the current draft of the standard, I saw that templates is its own chapter, so it might take a while to implement.)
The first FORTRAN released before the first COBOL. The most recent COBOL standard is dated 2023. I’ve used up my free Wired articles for the month so haven’t seen the article but if it can’t get these basics right one has to wonder about the content,
Could you elaborate on this point? My understanding and usage of submodules in Fortran is in no way linked to OO (at least not in the sense of strongly related). The usage I’ve seen and practiced is more related to separation of interfaces and implementation of logic, which has served as a path for cleaner structuring and faster build times. In your view, how would Modules + OO solve a functional split of interface and implementation?
I will add that it was already clear in the 1980ies that this is a troublesome feature. The group of Barbara Liskov (of Liskov Substitution Principle fame) had warned against the adoption of implementation inheritance at that time.
But Bjarne Stroustrup was a mentee of Kristen Nygaard, i.e. of one of the inventors of Simula 67, and hence of implementation inheritance. Thus, Stroustrup ignored these warnings.
You just described the raison d’être of modern-day (interface-/traits-based) OO. That’s the whole point of it.
You write your interfaces/traits as one set of modules (each of them occupying its own module and file), and you have your actual implementations “implement” (or inherit) these interfaces/traits in a separate set of modules. Only allowing for interfaces/traits as dependencies in this second set.
In this way, you can make your implementations depend only on modules that make up the first set (i.e. the interface set), but not the second (the implementation set, for details see the Traits proposal).
There’s absolutely no need to codify this as two different sets of modules (“modules” and “sub-modules”) in the language.
The latter approach makes the language itself needlessly complex. It also has the huge drawback that you cannot exchange or use, in the same program, different implementations that implement the same interface. Only the former (OO) approach, that is based on polymorphic interfaces/traits, allows you to do that.
My understanding of submodules is they were introduced to fix two problems, compiler cascades and circular module references. As long as you didn’t change your interfaces and you didn’t need the data encapsulation features that came with CONTAINING your code in the module, you have been able to separate interfaces (and reduce compiler cascades) from code since Fortran 90. You are just treating the module as something similar to a C header file. Not sure how you fix the circular reference problem though. In the years between Fortran 90 and 2003 there were several researchers who championed an “object based” approach built around modules which forced you to favor composition and aggragation over pure inheritance. For many applications I still prefer that approach to the current Fortran OOP facility.
You can do all of that (and more) with the OO approach that I described.
Submodules are an idea of the 1970ies, when computer scientists were exploring abstract data types, or ADTs, (what you called the “object-based approach”, which in reality it isn’t because objects differ from classical ADTs in that the former provide polymorphism).
Niklaus Wirth’s Modula-2 was a data abstraction language (i.e. it was based on ADTs). It was not a true object-oriented language (it lacked polymorphism). To separate interfaces from implementation for these ADTs he introduced two sets of modules, i.e. “interface” and “implementation” modules.
Later, when he went fully object-oriented with his Oberon language, he abandoned this distinction, because it became superfluous. Fortran 90, and later Fortran 2008, chose to fully copy Modula-2. Hence, we now have (superfluous) sub-modules in the language.
You actually can do that, and do so by combining two of the features you so despise.
module exterminator_m
implicit none
integer, parameter :: POISON_TYPE_DEFAULT = 1
integer, parameter :: POISON_TYPE_BETTER = 2
type :: exterminator
!...
procedure(with_default_poison), pointer :: poison => with_default_poison
!...
end type
interface exterminator
module procedure exterminator_new
end interface
interface
module subroutine with_default_poison(this)
class(exterminator), intent(inout) :: this
end subroutine
end interface
interface
module subroutine with_a_better_poison(this)
class(exterminator), intent(inout) :: this
end subroutine
end interface
contains
function exterminator_new(CHOOSE_YOUR_POISON) result(new)
type(exterminator) :: new
integer, optional, intent(in) :: CHOOSE_YOUR_POISON
if (present(CHOOSE_YOUR_POISON)) then
select case (CHOOSE_YOUR_POISON)
case (POISON_TYPE_DEFAULT)
continue
case (POISON_TYPE_BETTER)
new%poison => with_a_better_poison
case default
error stop 'this would be unnecessary once enumeration types kick in'
end select
endif
!...
end function
end module exterminator_m
submodule (exterminator_m) sm_defaults
implicit none
contains
module procedure with_default_poison
!...
end procedure
end submodule sm_defaults
submodule (exterminator_m) sm_better
implicit none
contains
module procedure with_a_better_poison
!...
end procedure
end submodule sm_better
As said elsethread, submodules solve a cascade compilation issue, not an OO one —and in reality, you can think of OOP in Fortran as a secondary add-on for the sake of organizing things, and somehow standardizing the (at the time) existing practice… but OOP is not a primary focus of the language.
In Fortran 90/95:
type :: base
!...
end type
type :: extended
type(base) :: base
!...
end type
In Fortran 2003+ (“extends” is just a fancy way of “embedding”):
type :: base
!...
end type
type, extends(base) :: extended
!...
end type
So, instead of breaking stuff in a Python-esque fashion, they just chose to work with what was already in the language —although they missed adding interface-like implementation behavior, like Java eventually did.
Although, I give you points on PDTs being sort of unusable.
This is how Wirth implemented dynamic dispatch (with procedure-pointers, or what he called “procedure variables”) in the first version of Oberon. Only to replace it later, in his work with Mössenböck on Oberon-2, by polymorphic dispatch, which was much safer (as it delegates all of the marshaling of the procedure pointers to the language instead of the programmer).
My point is that the language is already what it is, and that won’t change if it breaks stuff (so working with the existing features is the only option).
And also, that the language doesn’t revolve around OOP —e.g., the proposed templates have a clause about multiple instantiations of a derived type that might refer to the same thing, but that’s about it.
Just a odd note from a dusty past; in the 70’s, IBM put out a new language PL1 combining the “best” of cobol, Fortran, and another (Algol maybe?). Also they provided a translator to convert Fortran into PL1, which I used for my quantum mechanics code. It did compile, but unlike the Fortran version, the translated PL1 code did not fit into the mainframe memory.
And my point is that the designers of modern Fortran didn’t understand what they were copying into Fortran, because they didn’t care to study how Niklaus Wirth’s work had actually evolved.
Modern-day OO is the evolution and logical consequence of all the attempts at decoupling interfaces from implementation that started back in the 1960ies and 1970ies.
A language that “doesn’t revolve around OOP”, as you say, must then remain confined to 1960ies and 1970ies technology. In our technology-driven society, our many computational scientists deserve much better than that.
And no, working with the existing features is not “the only option”. See our Traits proposal.
No offense but I challenge you to provide one concrete example in the field of scientific computing where the reality of OOP has lived up to the hype. We went to the moon without OOP and could just as easily reach the stars without OOP. Don’t get me wrong. I don’t dismiss all of OOP. I just apply it in the areas where it makes a little sense to use it. I think all Fortran programmers can benefit from reading a good Design Patterns book. I just don’t believe that OOP should be the end all and be all of scientific programming. Anyone who has embarked on an OOP journey with Fortran will reach a point in their developement phase where they realized they could have had simpler code that was easier to maintain (if you are willing to commit to rigid coding standards, peer review of code, and standardized code maintenance practices) and more performant if they had just written it in good old procedural fashion.
There are simple examples in the mentioned Traits proposal. Of course, these won’t adequately convey the magnitude of the benefits that the approach entails for code bases of tens- or hundreds-of-thousands of code lines.
That would require me to write a book on the topic. I might actually do so if there’s enough interest in that. So, let me know.
A well-known OO code base from the C++ world are the Kokkos libraries, that seem to be very successful in what they are providing (but which I haven’t used myself).
One thing, that I believe @rouson has already mentioned in this forum, is that the consistent separation of interfaces from implementation leads to such reductions in code rigidity and “viscosity” that it becomes almost trivial to isolate and fix bugs, including compiler bugs (and to come up with the minimal reproducers that the latter require for their correction).
This is just one of the benefits. There are many more. Robert C. Martin discusses them in this talk that he gave at Yale (starts at the 12:28 mark, highly recommended):
Edits: to add the links to Kokkos and Martin’s talk.
Returning this thread to its original subject, I just checked the GCC site to check the current status of gcc-16 (for some reason a gcc-16 library appeared in my Linux Mint Update Manager even though 16 hasn’t officially been released) and was surprised to find that there is now a COBOL front end for GCC. There is also a Rust frontend under development.
Since you are a CFD person, I’ll give you another example. Consider the task of writing a CFD solver that must work with both structured and unstructured grids.
With OOP you can design a set of interfaces that will serve as the seams in your application where you would like to be able to swap in and out different implementations (e.g. structured grid dependent code, vs. unstructured grid dependent code) that you can then implement separately without any interference with each other.
You can actually design that whole thing in such a fashion that the parts that don’t depend on grid related data (e.g. your Riemann solver) can be reused in both settings.
You can then take that whole CFD solver, and by replacing a few sub-object building blocks with others, you can turn it into something almost completely different without changing any of the existing code (i.e. you just add new plugins to it, which is called the Open/Closed Principle). You can thus turn it easily into a radiation transport solver that will work on both structured and unstructured grids, etc.
Try to do that in “good old procedural fashion”. ![]()