Reasonml中的->和|>有什么区别?

时间:2019-04-02 12:16:49

标签: ocaml pipeline reason bucklescript

一段时间的密集搜索为我提供了一些示例,其中人们在一个代码中同时使用两种类型的运算符,但通常它们看起来就像两种做一件事情的方式,甚至具有相同的名称

2 个答案:

答案 0 :(得分:12)

tl; dr:的区别是->传递到第一个参数,而|>传递到最后一个参数。那就是:

x -> f(y, z) <=> f(x, y, z)
x |> f(y, z) <=> f(y, z, x)

不幸的是,有些细微之处和含义使这在实践中变得更加复杂和混乱。当我尝试解释其背后的历史时,请忍受我。

在管道寿命之前

在出现管道运算符之前,大多数函数式程序员使用“对象”设计了大多数函数,该函数将函数作为最后一个参数。这是因为使用部分函数应用程序使函数的编写更加容易,如果未应用参数放在末尾,那么使用咖喱语言可以使部分函数应用程序变得更加容易。

咖喱

在一种咖喱语言中,每个函数都只接受一个参数。看起来带有两个参数的函数实际上是一个带有一个参数的函数,但是随后返回另一个带有另一个参数的函数,并依次返回实际结果。因此,这些是等效的:

let add = (x, y) => x + y
let add = x => y => x + y

或者,第一种形式只是第二种形式的语法糖。

部分功能应用程序

这也意味着我们只需提供第一个参数就可以轻松地部分应用函数,这将使它返回一个在生成结果之前接受第二个参数的函数:

let add3 = add(3)
let result = add3(4) /* result == 7 */

我们必须将其包装在一个函数中,而无需麻烦,该函数会更加麻烦:

let add3 = y => add(3, y)

聪明的功能设计

现在事实证明,大多数函数都在“ main”自变量上操作,我们可以将其称为函数的“对象”。 List函数通常在特定列表上运行,例如,一次不会运行多个(当然,当然也会发生)。因此,将main参数放在最后可使您更轻松地编写函数。例如,使用几个经过精心设计的函数,定义一个将可选值列表转换为具有默认值的实际值列表的函数非常简单:

let values = default => List.map(Option.defaultValue(default)))

虽然首先使用“对象”设计的函数需要您编写:

let values = (list, default) =>
  List.map(list, value => Option.defaultValue(value, default)))

管道时代的曙光(具有讽刺意味的是,这并非管道优先)

据我了解,有人在F#中玩弄一种常见的流水线模式,认为要么为中间值提供命名绑定,要么使用太多该死的括号将函数调用向后嵌套,这很麻烦。因此,他发明了管道前导运算符|>。这样,管道可以写为

let result = list |> List.map(...) |> List.filter(...)

代替

let result = List.filter(..., List.map(..., list))

let mappedList = List.map(..., list)
let result = List.filter(..., mapped)

但这仅在main参数为last时有效,因为它依赖于通过currying的部分函数应用。

然后... BuckleScript

然后是Bob,他是第一作者BuckleScript的作者,目的是将OCaml代码编译为JavaScript。原因使BuckleScript被采用,然后Bob继续为BuckleScript创建一个名为Belt的标准库。 Belt忽略了上面我通过将主要参数放在第一位来解释的几乎所有内容。为什么?这有待解释,但据我所知,主要是因为JavaScript开发人员更熟悉 1

Bob确实认识到管道运算符的重要性,因此他创建了自己的管道优先运算符|.,该运算符仅与BuckleScript 2 一起使用。然后,Reason开发人员认为这看起来有些丑陋且缺乏方向,因此他们提出了->运算符,该运算符转换为|.并像它一样工作...除了它的优先级不同之外因此不能与其他任何东西配合使用。 3

结论

管道优先运算符本身并不是一个坏主意。但是在BuckleScript中实现和执行它的方式引起了很多混乱。它具有出乎意料的行为,会鼓励不良的功能设计,除非一概而论 4 ,并且在根据调用的函数类型在不同的管道运算符之间进行切换时会施加沉重的认知负担。 / p>

因此,我建议避免使用管道优先运算符(->|.),而应将管道前移(|>)与placeholder argument(也专用于原因)是否需要通过管道传递至“对象”优先功能,例如list |> List.map(...) |> Belt.List.keep(_, ...)


1 在与类型推断的交互方式上也存在一些细微的差异,因为类型是从左到右推断的,但这对任何一种样式IMO都不明显。

2 因为它需要语法转换。与管道转发不同,它不能仅作为普通运算符实现。

3 例如,list |> List.map(...) -> Belt.List.keep(...) doesn't work as you'd expect

4 这意味着无法使用几乎所有在管道优先运算符存在之前创建的库,因为这些库当然是在考虑原始管道转发运算符的情况下创建的。这有效地将生态系统一分为二。

答案 1 :(得分:6)

|>通常称为“管道转发”。它是一个辅助功能,已在更广泛的OCaml社区中使用,而不仅仅是ReasonML。它将左侧的参数作为 last 参数“注入”到右侧的函数中:

0 |> f       == f(0)
0 |> g(1)    == g(1, 0)
0 |> h(1, 2) == h(1, 2, 0)
// and so on

{->被称为“管道优先”,它是一种新的语法糖,它将左侧的参数注入到函数 or first 参数位置。 em>右侧的数据构造函数:

0 -> f       == f(0)
0 -> g(1)    == g(0, 1)
0 -> h(1, 2) == h(0, 1, 2)
0 -> Some    == Some(0)

请注意,->特定于BuckleScript,即在编译为JavaScript时。编译为本机时不可用,因此不可移植。此处有更多详细信息:https://reasonml.github.io/docs/en/pipe-first