Another typical use of recursive modules is to create recursion between a data type and a class type.

For these uses, recursive modules introduce heavy syntax, type-checking subtleties which are hard to understand for the programmer. And recursive modules are not yet fully understood/supported (i.e. there are several pending bugs with no clear resolution plan).

I wonder whether a more basic language feature wouldn't be better for those typical uses of recursive modules mentioned above: forward type declarations. The idea is to allow declaring a type name in a structure (or signature) but only give its actual definition later in the same structure. In between, the type is considered as being abstract.

We could introduce an explicit syntax:

type t = forward
...
type t := ...

or simply use the syntax for abstract type declaration:

type t
...
type t = ...

(Currently, this is rejected, because a type name can only be defined once in a structure/signature, so this does not change the meaning of existing well-typed programs.)

Here I assumed that it would be ok to define a forward type as a type abbreviation. I'm not 100% sure this would be safe. It not, we could only allow concrete definitions, but we would still be able to write:

On the implementation side, the basic strategy could be to reuse the same Ident.t as for the forward type when inserting the concrete declaration in the environment. I haven't fully thought about the implications of this proposal, maybe there are some tricky aspects to it, but I've the intuition that this feature would be easier to understand (both for users and for language developers) than recursive modules.

Jacques: I guess that if we can keep in the environment the information that the type is "currently" a forward declaration and not really an abstract type, this would address the problem with the typing of GADTs. Do you agree?

In relation to the discussion in PR#5985 (I'm not saying the issues themselves are related), I have grown a mistrust for the use of "abstract types defined in the current module" for strong inequalities when used as phantom types of GADTs: this property cannot be properly propagated through a signature boundary, and therefore breaks composability. My guess would be that this use of abstract-implementation types will become deprecated before any decision is made on this forward-declaration feature.

I have a mild sympathy with the proposal. There are a lot of satellite proposals of foward-declaration stuff, notably:

- some problems we have when playing with 'include' at the module level, where it is correct to shadow a let/val, but not a type or module declaration; this forces us to use subtle signature tricks when sometimes the redundancy is obviously safe, because the two declarations are identical or in a subtyping relation (one is more general than the other) and we could give a natural meaning to the redeclaration (take the least-upper-bound in precision)

Maybe none of them seems vital enough to warrant a language change, but together they paint a picture of a feature where signature items are accepted as structure items, and (except for let-and-let where shadowing is resolved by ordering) the static semantics of such repetitions is the least upper bound in precision.

(I would be fine with arbitrary restrictions designed to avoid any change in the *dynamic* semantics, in particular requiring that a 'val foo : ...' is always followed by a structure item introducing the corresponding 'let foo = ...', to avoid any recursion considerations.)

For values, the second declaration must be an instance of the first one, but the first one should be exported in the interface:

- val x: ...
- let x = ...

For my proposal of forward types, the second declaration is again an instance of the first one, but it should be the second one which should be exported in the interface (the interface could itself use a forward declaration if needed):

My idea was to take in any case the "intersection" (that's what I maybe confusingly called least upper-bound) of "definedness" of both sentences (forgetting about the let-shadowing behaviour). If `val` provides a precise type (polymorphic or monomorphic) but no dynamic meaning, and `let` provides a "less defined" inferred type with inferred type variables, but a dynamic meaning (a program term), the end result would be the precise typing of the `val` combined with the precise dynamic semantics of the `let`. And as you know that from the start, you can even propagate the annotation of the `val` when type-checking the `let`.

Similarly, you could define a variance or injectivity annotation in the "forward declaration" of the type, and have it checked against the latter definition, and incorporated into the final signature.

I think this "intersection of definedness" idea is consistent with both forward types and val+let. But I don't want to torpedo your proposal with additional debatable things.

Another example where you want to introduce circularity between type declarations is between module types and data types. For the syntax can't the "rec ... and" construct be extended to different kind of definition? (I used "and type" "and let" because otherwise it seems that give rise to ambiguity)

I'm wondering if our type and module system experts have an opinion about the usefulness and feasibility of this "forward-type declaration" proposal. Xavier, Jacques? Providing a simpler replacement to many uses of recursive modules sounds very compelling to me.

To be honest, I don't like this kind of approach, which makes the static semantics work as if it were dynamic.
And I wouldn't expect Xavier to like it more.
Remember also all the problems we already have with abstract types. This would only add more.

This said, extensible types proposed by lpw25 allow to do that, and I find it less shocking in that context.
In particular extensible types are variant types from the beginning, so they are less "unknown".

But in essence, what I'm asking for is a restriction of extensible types with exactly one single extension (and it must be in the structure defining the type). With this restriction, the spurious pattern matching disappear.

Given the number of small bugs and issues with recursive modules, with no resolution plan, I feel more and more uncomfortable relying on them. Moreover, it doesn't feel right to rely on the module system to allow recursion between base language features (e.g. recursion between a class and a data type).

Yes, this is a form of abstraction.
And abstraction requires a binder and a scope.
Here they are indicated by

type t = forward
...
type t := ...

This is fundamentally different from all the other abstractions you get in the language.

If the problem is that recursive modules are not specified, then maybe we should get Xavier in a corner, and ask for some subset of recursive modules that are guaranteed to work. For me one point of recursive modules (and first class modules) is that they avoid cluttering the base language with ad hoc features to express things that are better understood at the module level.

By the way, Bobot's proposal would be very nice, the only trouble is that we don't know how to implement it. There are just too many interactions to allow it without requiring type annotations.

In general, it would be nice if all definitions could be preceded by `and` to be used in a mutually recursive set of definitions, even if some of them (e.g. modules) required explicit type annotations.

As a side note, it would also be good if all definitions could be done locally using a syntax similar to local modules (e.g. `let exception E of int in`).