How do you deal with circular bindings references in sub modules?

module Foo = {
  type t
  @send external withBar: Bar.t => int = "withBar"
}

module Bar = {
  type t
  @send external withFoo: Foo.t => int = "withFoo"
}

Is this possible without additional files?

If your modules are organized into files, then you will need to define your types in a separate file.

// Types.res (or whatever you call it)
type foo
type bar

// Other files:
module Foo = {
  type t = foo
  @send external withBar: bar => int = "withBar"
}

module Bar = {
  type t = bar
  @send external withFoo: foo => int = "withFoo"
}

Recursive modules aren’t possible on the file level.

4 Likes

If they are nested submodules in the same file, they can be made mutually recursive with some extra type annotations:

module rec Foo: {
  type t
  @send external withBar: Bar.t => int = "withBar"
} = Foo
and Bar: {
  type t
  @send external withFoo: Foo.t => int = "withFoo"
} = Bar
4 Likes

Note that recursive modules are under consideration for removal in v11

2 Likes

Any idea why that may be? Types depending on each other is pretty common isn’t it?

The roadmap containing ? after a lot of line items indicates that it hasn’t really be thoroughly thought through or finalized. IMO as a newcomer to rescript that roadmap does more harm than good, it seems to indicate that there is no plan for the future of the language beyond a few “maybe?” questions.

1 Like

Types, yes. Modules no. I don’t want to speculate on why it might be removed; I don’t recall any discussion of it on this forum.

It’s quite possible there isn’t. The team is very small, and they copped a lot of heat for the way their rebranding went down in 2020. Maybe some of them burnt out in the second half of 2021. The primary compiler developer announced he was taking time off after the birth of his first child.

I don’t see these as huge problems, but it does mean community members need to nurture the community for the moment. I have faith things will get back to normal eventually; the people on the team are too passionate about the project to let it languish.

3 Likes

It’s been a tough couple years for everyone, I’m sorry if that came off as overly critical, I didn’t intend that. I hope that as I learn more I can become one of those people passionate about this project and make some meaningful contributions. IMO rescript is absolutely amazing and is the type of language we need for the future of web/mobile apps.

Why might removing recursive modules be a benefit? I’m still learning rescript so I may be missing something here but it seems a common pattern for modules to have a type t as their primary working type. If it’s no longer possible to have modules that mutually share types doesn’t that make it pretty tough to express a lot of programs?

1 Like

My guess is recursive modules are a difficult feature to maintain. In previous versions their JavaScript output was less clean than it is now, though, so the I’m not sure exactly what the tradeoffs are now.

Without recursive modules as a feature, it’s more difficult but still possible to express circular dependencies in nested modules. E.g. with a few changes to the above,

module MakeFoo = (Bar: { type t }) => {
  type t
  external fromBar: Bar.t => t = "%identity"
}

module MakeBar = (Foo: { type t }) => {
  type t
  external fromFoo: Foo.t => t = "%identity"
}

type foo = int
type bar = int

module Foo = MakeFoo({ type t = bar })
module Bar = MakeBar({ type t = foo })

let i1 = Foo.fromBar(1)
let i2 = Bar.fromFoo(2)
2 Likes

I’ve been working professionally with Rescript and OCaml for years and never had to use recursive modules.

In my opinion, @johnj 's answer is simple and clean:

and works also if the modules are inside the same file.

4 Likes

I can’t speak for the compiler devs, but see my comment here: Why not function/module declaration hoisting support? - #7 by johnj

I’m not especially in favor of removing recursive modules, but I think it’s worth noting that they’ve always been experimental, even in newer OCaml versions. Their exact definitions aren’t finalized, and their limitations (such as needing to annotate them) adds a lot of friction. In general, if a language makes it hard to use a feature, then you probably want to avoid that feature unless you have a good reason not to.

I know recursive modules have existed for a while, and some people use them, but I don’t think they’ve ever been especially recommended. Personally, I haven’t encountered a problem where recursive modules solved it better than recursive let rec bindings.

My point is that, whether the compiler supports it or not, I would always try other options before using recursive modules anyway.

2 Likes

I rarely create nested modules inside my module files, and I believe I’ve only used recursive modules once (on a side project, not my main codebase). I don’t see how the use of type t as the primary working type has anything to do with needing recursive modules.