I'm trying to figure out a way to hide certain helper functions and related stuff from the user of a module and thought using an IIFE would work, yet it fails because a type variable cannot be generalized?
I think I've boiled it down to the most basic scenario with the following code:
module TestA = {
let y = 0;
let x = (type a, numbers: list(a)): option(a) => None;
};
module TestB = {
let x =
(
() => {
let y = 0;
(type a, numbers: list(a)): option(a) => None;
}
)();
};
In TestB the compiler complains with
41 │ };
42 │
43 │ module TestB = {
44 │ let x =
. │ ...
50 │ )();
51 │ };
52 │
53 │ module Number = {
The type of this module contains type variables that cannot be generalized:
{ let x: list('_a) => option('_a); }
This happens when the type system senses there's a mutation/side-effect,
in combination with a polymorphic value.
Using or annotating that value usually solves it. More info:
https://realworldocaml.org/v1/en/html/imperative-programming-1.html#side-effects-and-weak-polymorphism
why is that? And how could I approach the problem of hiding y
from the user of the module?
P.s.: When reformatting the return type annotation in TestB
gets put behind the None
like so: (type a, numbers: list(a)) => (None: option(a))
. Why here and not in module TestA
? As far as I understood it this just "tags" the returned value, so I don't see a difference here?
You hit what is called the value restriction. In short, the compiler thinks there may be potential side effects happening inside the closure that might unsafely change the return type of the function. So it can't safely decide on a return type.
Fortunately, ReasonML has an easy, idiomatic replacement for IIFEs–brace-delimited scopes:
This scope hides
y
inside the definition ofx
.The more general way to hide items in a module is by giving the module a signature that simply does not list the hidden item(s). In this case it would look like:
Another way would be to use an 'interface file', e.g.: