【问题标题】:Understanding Js.Promise.resolve(. ) dot syntax in ReasonML理解 ReasonML 中的 Js.Promise.resolve(.) 点语法
【发布时间】:2018-12-06 11:20:51
【问题描述】:

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

在用法部分有:

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

为什么在 2 之前有点?它是什么意思,它有什么作用?

【问题讨论】:

  • 在您链接到的页面中提到,请参阅第一部分('Promise'),最后一段('这种类型签名意味着......')并点击'uncurried callbacks'链接.
  • 谢谢,我不知何故错过了那行。所以看起来这是一种阻止接受任何可能被柯里化的函数的方法。 “注意:声明站点和调用站点都需要有 uncurry 注释。这是保证/要求的一部分。”
  • 是的,它是一种语法糖,允许保持 OCaml 对函数的正常自动柯里化语义,同时轻松地允许与未柯里化的 JS 函数进行互操作。

标签: promise reason bucklescript


【解决方案1】:

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

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

好吧,那到底是什么意思? Wweeell,这是一个故事。如下:

什么是 uncurrying?

Uncurrying 是 currying 的反义词,所以我们先解释一下,然后比较一下。

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

在支持一流函数的非柯里化语言中,您还可以手动柯里化函数。让我们看一个 ES6 中的示例。这是一个普通的“非咖喱” ES6 函数:

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

这是它的咖喱形式:

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

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

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

第一个函数接受参数a,然后返回一个函数(结束于a),该函数接受参数b,然后计算最终返回值。

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

let result = add(2)(3);

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

这一切都很好! ...只要每个函数都被柯里化。但是后来我们想和 JavaScript 一起出去玩,大多数函数都没有(但请参阅 Ramda 了解一个值得注意的例外)。为了能够调用非curried JavaScript 函数,我们需要一个非curried 调用约定,并且为了能够创建可以按预期从JavaScript 调用的函数,我们需要一个非curried 函数类型 .

为什么resolve 需要不咖喱?

一个更好的问题可能是“为什么不是所有的外部函数都是 uncurried”?答案是它们实际上是,但类型和调用约定通常都可以在编译时推断出来。如果不是这样,它通常可以通过检查函数值在运行时“反映”,而性能成本很小(但很快就会复合)。例外情况是它变得有点混乱,因为文档没有准确解释何时需要显式 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,因为所有externals当然都是用JavaScript实现的。

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

第三种函数是resolvereject,它们是从JavaScript传回的回调函数,因此是非curried的。但这些也是一元函数,您认为咖喱和非咖喱形式应该完全相同。使用普通的单态函数是正确的,但不幸的是resolve 是多态的,这会产生一些问题。

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

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

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

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


有关更多信息,请参阅the manual(但请注意,它会将柯里化与部分应用混淆)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-02
    • 1970-01-01
    • 2019-11-26
    • 2018-06-18
    相关资源
    最近更新 更多