为什么在 iife 中包装函数会导致弱类型?

why does wrapping functions in iife cause weak types?

我正在尝试找出一种方法来对模块的用户隐藏某些辅助函数和相关内容,并认为使用 IIFE 会起作用,但它失败了,因为类型变量无法泛化?

我想我已经使用以下代码将其归结为最基本的场景:

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;
      }
    )();
};

在 TestB 中,编译器抱怨

  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

这是为什么呢?我该如何处理对模块用户隐藏 y 的问题?

P.s.: 重新格式化 TestB 中的 return 类型注释时,会像这样放在 None 后面:(type a, numbers: list(a)) => (None: option(a))。为什么在这里而不是在模块 TestA 中?据我了解,这只是 "tags" 的 returned 值,所以我看不出这里有什么不同?

你打什么叫value restriction。简而言之,编译器认为闭包内部可能会发生潜在的副作用,可能会不安全地更改函数的 return 类型。所以它不能安全地决定 return 类型。

幸运的是,ReasonML 有一个简单、惯用的 IIFE 替代品 –brace-delimited scopes:

module TestA = {
  let x = {
    let y = 0;
    numbers => None;
  };
};

此范围将 y 隐藏在 x 的定义中。

在模块中隐藏项目的更通用的方法是为模块提供一个简单地不列出隐藏项目的签名。在这种情况下,它看起来像:

module TestA: {
  let x: list('a) => option('a);
} = {
  let y = 0;
  let x = numbers => None;
};

另一种方法是使用 'interface file',例如:

// TestA.rei
let x: list('a) => option('a);

// TestA.re
let y = 0;
let x = numbers => None;