I believe this is very unlikely for a variety of reasons. (I’m not an expert on how the compiler works, so anyone feel free to correct me or clarify this.) There are technical as well as theoretical reasons for why recursion is opt-in. The use case you mention specifically, using mutual recursion across file-level modules, is impossible AFAIK. Mutually recursive modules are already one of the most complicated parts of the compiler, which is why they are almost always discouraged.
If you’re porting mutually recursive JS functions from different files, it would be much easier to just put the ReScript versions together in one module. Another general-purpose way to cut a recursive knot is to use callback arguments, so one function can send itself as a callback to another function, and vice-versa.
As far as value-level mutual recursion (let rec ... and
), I don’t think it would work as opt-out instead of opt-in. If everything was mutually recursive by default, then you would lose core features like shadowing, and type inference would be worse.
One way to understand ReScript code is that every let-binding creates a new scope. Just like a manual { }
local scope, any expression is able to “see” the values in the scopes outside of its own, but can’t “see” into its inner scopes. In code like this:
let x = 1
let x = x + 1
both x
values coexist in their own scopes. The first x
can’t see the second x
, but the second can see the first. (And each x
can’t see itself, either.)
When you use let rec ... and
you’re telling the compiler “don’t create a new scope after this binding.” So then the values will all share the same scope, and thus be able to see each other. This has tradeoffs, though. Our previous example can’t compile if it’s recursive (due to shadowing). let rec x = 1 and x = x + 1
raises a compile error.
I’m sure someone else can explain the technical aspects better than I can, but I hope that somewhat explains why recursion works the way that it does.
And this part is just my opinion, but I believe that it’s better to think of this as a feature rather than a limitation. One core selling point of FP languages is that they encourage simple, elegant solutions. They make it easier to write “good” code and harder to write “bad” code (as subjective as that may be). Mutual recursion has its place, but it makes your code more complex and harder to maintain. Even when I’m writing JS, I don’t want to spread mutually-recursive functions across different files. If you only enable it when it’s necessary, you’ll help keep your code a lot cleaner and more readable (for both humans and the compiler).