繁体   English   中英

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

[英]What's the difference between -> and |> in reasonml?

一段激烈的谷歌搜索为我提供了一些例子,人们在一个代码中使用两种类型的运算符,但通常它们看起来就像两种方式做一件事,他们甚至有相同的名字

tl; dr:定义的区别是->管道到第一个参数,而|>管道到最后一个。 那是:

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

不幸的是,有一些细微之处和含义使得这在实践中变得更加复杂和混乱。 当我试图解释它背后的历史时,请耐心等待。

在管道时代之前

在有任何管道运算符之前,大多数函数式程序员使用“对象”设计大多数函数,该函数作为最后一个参数运行。 这是因为部分函数应用使函数组合变得更容易,并且如果未应用的参数最后,则在curry语言中部分函数应用变得更容易。

哗众取宠

在一种curry语言中,每个函数只需要一个参数。 一个看起来带有两个参数的函数实际上是一个接受一个参数的函数,但是然后返回另一个接受另一个参数的函数,然后返回实际的结果。 因此这些是等价的:

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

或者更确切地说,第一种形式只是第二种形式的语法糖。

部分功能应用

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

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

如果没有curry,我们必须将它包装在一个函数中,这更加麻烦:

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

巧妙的功能设计

现在事实证明,大多数函数都在“主”参数上运行,我们可以称之为函数的“对象”。 List函数通常在特定列表上运行,例如,不是一次运行(当然,确实也会发生)。 因此,将主要参数放在最后使您可以更轻松地编写函数。 例如,通过一些设计良好的函数,定义一个函数将可选值列表转换为具有默认值的实际值列表,就像这样简单:

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)

但这只有在主要参数是最后一个时才有效,因为它依赖于通过currying进行部分函数应用。

然后...... BuckleScript

然后是Bob,他首先创作了BuckleScript,以便将OCaml代码编译为JavaScript。 Reason收录了BuckleScript,然后Bob继续为BuckleScript创建一个名为Belt的标准库。 Belt忽略了几乎所有的东西我已经把主要的论点第一如上所述。 为什么? 这还有待解释,但我可以收集它主要是因为它对JavaScript开发人员来说更为熟悉1

然而,Bob确实认识到管道操作员的重要性,因此他创建了自己的管道优先运营商, |. ,仅适用于BuckleScript 2 然后原因开发人员认为看起来有点丑陋且缺乏方向,所以他们提出了->运算符,转换为|. 并且工作完全像它...除了它有不同的优先权,因此不能与其他任何东西好玩。 3

结论

管道优先运算符本身并不是一个坏主意。 但它在BuckleScript和Reason中实现和执行的方式引起了很多困惑。 它有意想不到的行为,鼓励糟糕的功能设计,除非一个人全力以赴4 ,根据你所调用的功能,在不同的管道操作员之间切换时会产生沉重的认知税。

因此,如果你需要管道到“对象” - 第一个函数,我建议避免使用管道优先运算符( ->|. ),而是使用带有占位符参数的管道转发( |> )(也不包括Reason) ,例如list |> List.map(...) |> Belt.List.keep(_, ...)


1这与类型推断的交互方式也存在一些微妙的差异,因为类型是从左到右推断出来的,但这对IMO的风格都没有明显的好处。

2因为它需要语法转换。 与管道前进不同,它不能仅作为普通的操作员实现。

3例如, list |> List.map(...) -> Belt.List.keep(...) 无效,正如您所期望的那样

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

|>通常称为“管道前进”。 它是一个辅助功能,用于更广泛的OCaml社区,而不仅仅是ReasonML。 它将左侧的参数“注入”作为右侧函数的最后一个参数:

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

->被称为'pipe-first',它是一个新的语法糖,它将左边的参数注入到右边的函数数据构造函数的第一个参数位置:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM