理解 ReasonML 中的 Js.Promise.resolve(. ) 点语法

Understanding Js.Promise.resolve(. ) dot syntax in ReasonML

我正在尝试理解文档: https://reasonml.github.io/docs/en/promise

在用法部分有:

let myPromise = Js.Promise.make((~resolve, ~reject) => resolve(. 2));

为什么2前面有个点?这是什么意思,它有什么作用?

(. ) 在这里,在函数应用中,意味着函数应该以非柯里化的调用约定来调用。

在函数类型中使用时,如这里resolve的类型,(. 'a) => unit表示该函数是非柯里化的。

好吧,那到底是什么意思? Wweeellll,这是一个故事。开始:

什么是 uncurrying?

Uncurrying 与 currying 相反,所以让我们先解释一下再比较。

Currying 是将一个接受多个参数的函数转换为一系列函数的过程,每个函数只接受一个参数,return 最终的 return 值或接受下一个参数的函数.在 Reason/OCaml 中,这是自动为我们完成的,这就是为什么 OCaml 函数类型在其参数之间有箭头(例如 'a -> 'b -> 'ret)。您也可以在 Reason 中以这种方式编写函数类型 ('a => 'b => 'ret),但默认情况下它被语法 (('a, 'b) => 'ret) 隐藏,这本意很好,但也可能使理解函数的原因变得更加困难在某些情况下会出现意外行为。

在支持 first-class 函数的非柯里化语言中,您还可以手动柯里化函数。让我们看一个 ES6 中的例子。这是一个正常的 "uncurried" ES6 函数:

let add = (a, b) => a + b;

这是它的柯里化形式:

let add = a => b => a + b;

并用括号强调单独的功能:

let add = a => (b => a + b);

第一个函数采用参数 a,然后 return 是一个函数(关闭 a),它采用参数 b,然后计算最终的 return值。

这很酷,因为我们可以轻松地部分应用 a 参数而无需使用 bind,但是一次性应用所有参数有点不方便,因为我们必须调用每个函数个人:

let result = add(2)(3);

因此 Reason/OCaml 不仅在创建时自动柯里化函数,而且还提供了一个 调用约定 让我们也可以方便地应用多个参数。

这一切都很棒! ...只要每个函数都被柯里化了。但是后来我们想和 JavaScript 闲逛并交谈,而大多数功能都不是(但请参阅 Ramda 以了解一个值得注意的例外)。为了能够调用未柯里化的 JavaScript 函数,我们需要一个未柯里化的 调用约定 ,并且为了能够创建可以从 JavaScript 中按预期调用的函数,我们需要未柯里化的 函数类型 .

为什么 resolve 需要取消柯里化?

更好的问题可能是 "why aren't all external functions uncurried"?答案是它们确实是,但类型和调用约定通常都可以在编译时推断出来。如果不是,它通常可以在运行时通过检查函数值 "reflected",以很小(但很快复合)的性能成本。例外情况是它变得有点混乱,因为文档没有准确解释何时需要显式 uncurrying 才能正常运行,以及何时不需要但出于性能原因可能有益。此外,对于未柯里化的函数实际上有两种注释,一种可以推断调用约定,另一种需要它是显式的,就像这里的情况一样。但这是我收集到的。

让我们看看 Js.Promise.make 的完整签名,这很有趣,因为它包含三种未柯里化函数:

[@bs.new]
external make :
    ([@bs.uncurry] (
        (~resolve: (. 'a) => unit,
         ~reject: (. exn) => unit) => unit)) => t('a) = "Promise";

或者在 OCaml 语法中,我发现在这种情况下它的可读性要好得多:

external make : (resolve:('a -> unit [@bs]) ->
                 reject:(exn -> unit [@bs]) -> unit [@bs.uncurry]) -> 'a t = "Promise" [@@bs.new]

第一种函数是make本身,它是一个external,可以推断是uncurried,因为所有external当然都是在JavaScript.

中实现的

第二种函数是我们将创建并传递给 make 的回调。这必须是非柯里化的,因为它是从 JavaScript 调用的,具有非柯里化的调用约定。但是由于我们创建的函数默认是柯里化的,所以这里使用 [@bs.uncurry] 来指定它需要一个非柯里化的函数,并且它应该自动地非柯里化。

第三种函数是resolvereject,它们是JavaScript传回的回调函数,因此是非柯里化的。但这些也是一元函数,您认为柯里化和非柯里化形式应该完全相同。对于普通的单态函数,你是对的,但不幸的是 resolve 是多态的,这会产生一些问题。

如果 return 类型是多态的,函数实际上可能不是柯里化形式的一元函数,因为 return 值本身可能是一个接受另一个参数的函数,这可能 return 另一个函数等等。这是柯里化的一个缺点。但幸运的是它不是,所以我们知道它是一进制的。

我认为问题比那更微妙。它可能会出现,因为我们需要能够使用所有 1 元的柯里化函数类型来表示 0 元非柯里化函数。我们该怎么做?好吧,如果您要在 Reason/OCaml 中实现等效函数,您将使用 unit 作为参数类型,所以我们就这样做吧。但是现在,如果你有一个多态函数,如果它被单态化为 unit ,它可能是 0 元的,否则是 1 元的。我想用一个参数调用 0 元函数在某种程度上被认为是不合理的。

但是,既然 reject 不是多态的,那么为什么 reject 需要非柯里化呢?

嗯...我最好的猜测是这只是为了保持一致性。


有关详细信息,请参阅 the manual(但请注意,它混淆了局部应用)