Encouraged by this thread, I decided to simply prototype the namespaces in LFortran: Namespace imports for modules (use, namespace): prototype by certik · Pull Request #13800 · lfortran/lfortran · GitHub, here is a nice document describing how it works: lfortran/doc/src/namespace_modules.md at 801061698f57664ca0c9b238c1e7db51a35c8725 · certik/lfortran · GitHub, there are a lot of tests. I am still working on it, but so far I haven’t seen any blocker or a reason why this cannot work (because it works :).
Yes, this is what I meant.
The lack of qualified imports in the language (a la import numpy as np) can be overcome by binding all procedures (and the data they operate on) to derived types, and by using only the latter for imports. This amounts to consistent use of encapsulation/data abstraction.
I agree that if a language is supposed to support different programming paradigms, then it should provide all the necessary means to program in those paradigms.
I’d recommend to view this as far more than a hack, because it allows one to decouple one’s code by doing the next step in this logical chain. Namely, to forego even imports of single concrete types, and to use polymorphism in order to import abstractions (e.g. the future traits) instead, that concrete types must implement.
There is perhaps another, much simpler direction worth considering. Instead of trying to make .mod files smarter, or replacing them with a compiler-managed database, we can avoid making module files part of the build interface altogether.
My proposal is based on C’s #include model, using module + submodule. The convention is: M90 for interfaces and F90 for implementations. The implementation file includes its interface:
! Foo.F90
#include "Foo.M90"
submodule (Foo) FooImpl
contains
![...]
end submodule FooImpl
where
! Foo.M90
#ifndef FOO_MODULE
#define FOO_MODULE
module Foo
![...]
end module Foo
#endif
The compiler only needs to process implementation files. The important condition is simply:
Any implementation that would cause a symbol to be emitted into the corresponding object file must not be part of an interface (
M90) file.
This makes the interface effectively analogous to a C header, as it can be included in multiple translation units without producing duplicate linker definitions.
The consequence is that the build system no longer consumes .mod files. Dependency tracking can then be reduced to the usual C pattern: the build system tracks #include dependencies between source files. The price is that the interface is processed again for every translation unit that includes it. I think this is worth considering separately from the “let the compiler own the build database” approach.
Hmm…
C++ introduced modules, in part, to try to move away from header-guard-hell (and the #pragma once hack). And newer languages (like Go, Rust, Zig) were built around the idea of avoiding just that same hell/hack.
I agree that #include is not elegant. My point is that elegance is not the same thing as functionality. #include gives us a simple file composition mechanism to separate an interface from an implementation. Include guards may be a little tedious, but with a simple convention and discipline, they are not a significant obstacle.
The point of the thread is to bury .mod files, so I don’t see a need to create a new compilation architecture merely because Go/Rust/Zig solved a similar problem differently. We already have module and submodule; I’d rather exploit those than reinvent the wheel.
I asked Claude Code a question:
Q: btw, thinking about a system to keep Fortran MODULE and SUBMODULE information, I imagine an alternative that uses a per-project (software library or executable) database to keep track not only of timestamps for source files but also of “uses”. For instance, for each MODULE X, the database would record each individual USE statement (naming that same MODULE X) in any other source file/program unit/scope. Can you describe how that would cut down on unnecessary recompilations?
A: The key insight is that current build systems operate at the wrong granularity – they track file-level or module-level dependencies, but recompilation necessity is determined at the entity interface level. Here is how a USE-tracking database would help:
WHAT THE DATABASE RECORDS
For every compilation unit that contains a USE statement, it records not just “file B uses module M” but:
- Which specific entities from M are accessed (especially if USE M, ONLY: alpha, beta)
- Whether entities are renamed (USE M, local => M_name) and under which local name referenced
- The interface fingerprint of each entity at the time of last compilation of the consumer
For each module definition, it records the interface fingerprint of every public entity.
HOW UNNECESSARY RECOMPILATIONS ARE ELIMINATED
- Implementation changes that do not touch the public interface
A subroutine body can be rewritten entirely. If its dummy argument list, result type, and INTENT specifications are unchanged, its interface fingerprint is unchanged. Every consumer that USEs that subroutine can be skipped. - Adding new public entities
If a new subroutine is added to module M, consumers that do not reference it at all are unaffected. - Changes to private or internal entities
Modifications to private module variables, internal subroutines, or local TYPE definitions not exposed in any public interface fingerprint do not propagate at all. - Submodule bodies
A SUBMODULE implements MODULE PROCEDURE bodies – the interface is declared in the parent module and is fixed. Recompiling a submodule cannot possibly require recompilation of any USE consumer of the parent module. - ONLY-clause filtering
USE M, ONLY: alpha means the consumer is entirely insulated from changes to every other entity in M. - Rename isolation
USE M, local => M_name means only M_name’s fingerprint matters for that consumer.
THE PROPAGATION ALGORITHM
When source file F is recompiled and module M is regenerated:
- Compute the set D of interface fingerprints that changed
- If D is empty → no downstream action needed, regardless of file timestamp
- For each consumer C: compute intersection of D with entities C accesses from M
- If intersection is empty → skip C
- If non-empty → recompile C, update stored fingerprints, recurse through C’s consumers
COMPOUNDING BENEFIT ACROSS DEEP DEPENDENCY GRAPHS
In a hierarchy A uses B uses C uses D, a change deep in D affecting only its private implementation would currently force recompilation of C, then B, then A. With the database, the chain stops at D immediately.
RELATION TO EXISTING .MOD FILE APPROACHES
.mod files are binary/opaque and force an all-or-nothing decision. The database reasons at the level of individual entities, handles ONLY-clause filtering, and stores the consumer’s view of the interface rather than just what the module currently exposes.
I know it’s an LLM just being an LLM, but…
Isn’t that unnecessarily complicated? Full parsing, followed by git diff (for interfaces), followed by ONLY tracking (marginally useful, imho) —it’d be faster to just recompile the whole thing, hehehe.
My “slow human” approach would involve just having timestamps of sources and libraries/binaries, custom-preprocessing of sources (you can get module names and USE names from there), and tracking “library-x-provides-module-y”.
Since .mod files are opaque, I am not sure I have much of a view on them.
They could do more.
Why can’t there be a more formal requirement of the compiler to build an implicit project INTERFACE database, which could be supplemented and checked with explict INTERFACE and all calls.
I remember in about 1998, using this new F95, I developed an include file of INTERFACE for all routines in my project, only to get errors that the INTERFACE could not be included in itself. I thought it would have been a good debug facility.
Most compilers now do this in a non-standard way.
Prior to .mod files and modules, include files were very useful for minimising repeated definitions
As for partial recompile, I typically use a batch procedure for complete rebuild, as now few projects take longer that it takes to think if you are ready to test this new coding attempt !
Compilers have become very good and very fast.
Efficient recompilation may not be on your radar now, but if the AI revolution pans out and your agent is 24/7 searching the solution space, then I expect it to become the bottleneck.
I just can’t contemplate that.
My rebuild cycle is:
devise a change or correction to the code
implement the change
rebuild
modify the test suite to include the change
do the tests
review the tests
cycle ;
I am understanding you are suggesting that AI will speed up all but rebuild.
I am not so sure on some of the other steps I have suggested ![]()
I am however very impressed with what is claimed claude can do for step 1
Submodules (F2008-onward) allow you to do this.
To me, the main problems with .mod (and .smod) files are:
1.) Each compiler has its own internal details. So there is no interoperability.
2.) Even when using one compiler vendor, version mismatches can occur with newer compilers and older .mod/.smod files and vice versa.
Exactly - the primary issue with .mod files is they aren’t portable, which causes problems creating and distributing libraries built with Fortran. This is a particular problem since these are exactly the kinds of things for which we want a high-performance language like Fortran.
Most compilers support a “syntax-only” first pass which can generate mod files, allowing the main build to be done in a far more parallelisable second pass. There is an issue that some of the compilers are pretty slow at the parsing, but for a big project the two-pass method really speeds things up. We found the main ideas here:
I’m curious if anyone has ever suggested that this step become part of the fortran standard itself. The output would be the syntax tree and the module files (which contain the interfaces). It seems like there would be many useful tools that could be built on those outputs, and they would be portable because their contents and formats would be defined by the standard
Is this what LLVM is trying to accomplish?
BTW, when I saw the title of this thread, I wondered if its purpose was the same as the Mark Antony speech, namely to damn modules, but then to highlight all of their good features. ![]()
Speed of compilation has always been essential, that was one of my main motivations to enter the compiler field. AI just makes it obvious.
I used to support Modula 2 on a variety of platforms when I worked at Imperial College Computer Centre (1978-1986) and mod files weren’t portable then either, so the issue isn’t new.
I couldn’t disagree more. That is a non-existent issue. The source format is a perfectly good, portable solution, available right now, in all compilers.
The problem is that our compilers are still in a FORTRAN77/C mindset where a file is considered a significant unit and we had to shoehorn this mindset into a world of modules. The significant units, which determine the minimum necessary amount of work in recompilation, are much “smaller” entities.
Flang’s module files are stored as plain, human-readable Fortran text rather than a compiler-specific binary format, so they’re more transparent and in principle easier for another compiler to parse than most binary .mod formats. That said, full portability is probably unattainable regardless of the .mod file’s format: .mod files only capture interface/specification info, while the actual compiled code lives in object files. Unless those object files also share a common calling convention and ABI, you still can’t mix and match across compilers — so .mod format alone isn’t the real portability bottleneck.
Genuine question for the compiler experts. I have no bandwidth for this rn but I believe that in the AI age it would be really easy to wire a .mod reader of any possible .mod formats (how many can they be? I guess one per compiler, max 2-3 versions each, so 10? 20? perhaps even less) into an existing Fortran parser (i.e. LFortran).
Now, what would that enable? Could a compiler build code against .o objects that were compiled with different compilers? I guess that the data structures and procedure interfaces in the .mod file could be 100% reconstructed, and names fully mangled, but how about the data/vtable layout then?
I guess that if the data layout for the classes could also be dealt with - then BOOM, it’s a solved problem. Imagine MPI modules - vendor gives a compiler-tied version, LFortran+mod-converter does the rest.
@FedericoPerini yes, that can be done. LFortran had a reader for GFortran’s mod files. They change every couple versions, so you have to support all recent ones. Then you can learn what the ABI is for the object files, and then you can probably use it. But we stopped maintaining it, as it’s a lot of work to make it usable.