I just stumbled upon https://blog.ecosta.dev/en/tech/explaining-cpp20-modules
I think that it is well explained and highly intuitive. (Perhaps I am biased though...) C++ modules allow for module descriptions , the description itself is a module. The descriptive module is initiated by writing:
the module can be used by importing it :
export modules can have a companion, you might guess it -- a module :
he companion will not contain the export qualifier, it can't export something by itself.
The module system (three keywords : export, import and module that's it all) aims to replace C preprocessor definitions (no #define and #include anymore in modules). At the toplevel, these C PP commands will still be allowed.
A basic, if not the basic idea: Modules can be precompiled and kept cached for instant use by the compiler. C++ modules are regarded as the biggest change in C++ since its inception. C++ is one of the major competitors to Nim (Nim itself being compiled to C or C++ ...) and in one or another way, it will have an impact on Nim. Perhaps Nim code will be part of a C++ environment relying on modules. Than, "someone" has to write the module definition part.
Modules (Module definitions) could be parametrized. This is not implemented actually, but since almost anything in C++ evolves into a template, this could happen here too. Moreover, C++ modules impose their own type system anyway, being a template or not.
Moreover, C++ modules impose their own type system anyway, being a template or not.
How so? (Just because ML has something named modules and it's related to its type system that doesn't mean other module systems always impose a "type system".)
Moreover, C++ modules impose their own type system anyway, being a template or not.
What does that mean?
Without the export and module annotations, g++ will compile the following example. We have a module type (module structure) MS, and a module instantiation MI. None of these gets revealed to the outside. The module can be compiled separatedly, no dependencies with an importing program. :private tells the compiler that the export section is complete. The template <T>calc gets exported, but because the underlying module structure remains hidden, it can't be used from an importing program. However, the template defines some properties of the module.
export module M1;
class MS; //forward declaration
export typedef MS *M; // for exported typedef
export M init(int);
export M upd(M);
export template <typename T>
T calc(T mv) {
mv->r = mv->v;
return mv; };
module :private; // from now on no export decls anymore
struct N { string r,v;} ;
struct MS {
N a; int r,v;
M init(int v) { set(v,"start"); r = 0; a.r = ""; return this; }
M upd() { calc(&a); return calc(this); }
void set(int iv, string sv) { v = iv; a.v = sv;}
} MI;
M init(int v) { return MI.init(v); }
M upd(M mv) { return mv->upd(); }
I refer to the C++ example given above.
Because of the hidden module type (hidden outside the module), functions can't be applied to the module from the outside. They can only be injected and need to be converted (instantiated ) by the module itself. An instantiated function then makes a change from A->B to M[A->B] . The conversion step postponed, we want to use the function like this:
So,With the help of A->B, we can modify/update/extend the module. What type has M[A]? We obtain:
...Presumed that the instantiation of A->B succeeds. How does it look like with the template calc in the C++ program?
We have: calc :: (all T) T[_,v] -> T[v,v] (all T) is the universally bound T, so there is no free T in calc and (all T) is a scope delimiter. Therefore, an instantiated T will not affect the global type environment. v is a value with a value type V that depends on T, e.g. v :: T.V . For simplification, we treat values and terms as singletons here, the stand for themselves, so v :: v or N :: N .
Now, if we bind T to MS (MS is available in the module)
With v:: MS.V abbrev. v :: V :
success.... now we can apply the instantiated calc to the module with calc(this). Furthermore, MS contains a member a :: N. calc works exactly the same way here, but with a different instantiation:
We just modeled abstraction and implemented the existential quantifier at the same time.
(it is regarded as a seminal paper)
Now , we could add many functions the same way. The typing doesn't change if we implement functions directly without using a template. Abstraction means hidden quantifiers - even if we don't see them.
We could go further than that. How are modules related to each other? Do they allow for subtyping, instantiation subtyping, composition etc. ? These derivations become possible, if we resort to higher order unification. First order unification (like Hindley-Milner) might work too, but not in general.