简体   繁体   English

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

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

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

tl;dr: The defining difference is that -> pipes to the first argument while |> pipes to the last. tl; dr:定义的区别是->管道到第一个参数,而|>管道到最后一个。 That is: 那是:

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

Unfortunately there are some subtleties and implications that makes this a bit more complicated and confusing in practice. 不幸的是,有一些细微之处和含义使得这在实践中变得更加复杂和混乱。 Please bear with me as I try to explain the history behind it. 当我试图解释它背后的历史时,请耐心等待。

Before the age of pipe 在管道时代之前

Before there were any pipe operators, most functional programmers designed most functions with the "object" that the the function operates as the last argument. 在有任何管道运算符之前,大多数函数式程序员使用“对象”设计大多数函数,该函数作为最后一个参数运行。 This is because function composition is made much easier with partial function application, and partial function application is made much easier in curried languages if the arguments not applied are at the end. 这是因为部分函数应用使函数组合变得更容易,并且如果未应用的参数最后,则在curry语言中部分函数应用变得更容易。

Currying 哗众取宠

In a curried language, every function takes exactly one argument. 在一种curry语言中,每个函数只需要一个参数。 A function that appears to take two arguments is really a function that takes one argument, but then returns another function that takes another argument and in turn returns the actual result. 一个看起来带有两个参数的函数实际上是一个接受一个参数的函数,但是然后返回另一个接受另一个参数的函数,然后返回实际的结果。 Therefore these are equivalent: 因此这些是等价的:

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

Or rather, the first form is just syntax sugar for the second form. 或者更确切地说,第一种形式只是第二种形式的语法糖。

Partial function application 部分功能应用

This also means we can easily partially apply a function by just providing the first argument, which will have it return a function that accepts the second argument before producing a result: 这也意味着我们可以通过提供第一个参数来轻松地部分应用函数,这将使它返回一个在生成结果之前接受第二个参数的函数:

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

Without currying, we'd have to instead wrap it in a function, which is much more cumbersome: 如果没有curry,我们必须将它包装在一个函数中,这更加麻烦:

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

Clever function design 巧妙的功能设计

Now it turns out that most functions operate on a "main" argument, which we might call the "object" of a function. 现在事实证明,大多数函数都在“主”参数上运行,我们可以称之为函数的“对象”。 List functions usually operate on a specific list, for example, not several at once (although that does occur too, of course). List函数通常在特定列表上运行,例如,不是一次运行(当然,确实也会发生)。 And therefore, putting the main argument last enables you to compose functions much more easily. 因此,将主要参数放在最后使您可以更轻松地编写函数。 For example, with a couple of well-designed functions, defining a function to transform a list of optional values into a list of actual values with defaults is as simple as: 例如,通过一些设计良好的函数,定义一个函数将可选值列表转换为具有默认值的实际值列表,就像这样简单:

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

While functions designed with the "object" first would require you to write: 虽然首先使用“对象”设计的函数需要您编写:

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

The dawn of the pipe era (which, ironically, wasn't pipe-first) 管道时代的曙光(具有讽刺意味的是,它不是管道先发的)

From what I understand, someone playing around in F# discovered a commonly occurring pipeline pattern and thought it was cumbersome to either come up with named bindings for intermediate values or nest the function calls in backwards order using too many damn parentheses. 根据我的理解,在F#中玩游戏的人发现了一种常见的管道模式,并认为要么为中间值提供命名绑定,要么使用太多该死的括号以反向顺序嵌套函数调用,这很麻烦。 So he invented the pipe-forward operator, |> . 所以他发明了管道前移算子, |> With this, a pipeline could be written as 有了这个,管道可以写成

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

instead of 代替

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

or 要么

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

But this only works if the main argument is last, because it relies on partial function application through currying. 但这只有在主要参数是最后一个时才有效,因为它依赖于通过currying进行部分函数应用。

And then... BuckleScript 然后...... BuckleScript

Then along comes Bob, who first authored BuckleScript in order to compile OCaml code to JavaScript. 然后是Bob,他首先创作了BuckleScript,以便将OCaml代码编译为JavaScript。 BuckleScript was adopted by Reason, and then Bob went on to create a standard library for BuckleScript called Belt . Reason收录了BuckleScript,然后Bob继续为BuckleScript创建一个名为Belt的标准库。 Belt ignores almost everything I've explained above by putting the main argument first . Belt忽略了几乎所有的东西我已经把主要的论点第一如上所述。 Why? 为什么? That has yet to be explained, but from what I can gather it's primarily because it's more familiar to JavaScript developers 1 . 这还有待解释,但我可以收集它主要是因为它对JavaScript开发人员来说更为熟悉1

Bob did recognize the importance of the pipe operator, however, so he created his own pipe-first operator, |. 然而,Bob确实认识到管道操作员的重要性,因此他创建了自己的管道优先运营商, |. , which works only with BuckleScript 2 . ,仅适用于BuckleScript 2 And then the Reason developers thought that looked a bit ugly and lacking direction, so they came up with the -> operator, which translates to |. 然后原因开发人员认为看起来有点丑陋且缺乏方向,所以他们提出了->运算符,转换为|. and works exactly like it... except it has a different precedence and therefore doesn't play nice with anything else. 并且工作完全像它...除了它有不同的优先权,因此不能与其他任何东西好玩。 3 3

Conclusion 结论

A pipe-first operator isn't a bad idea in itself. 管道优先运算符本身并不是一个坏主意。 But the way it has been implemented and executed in BuckleScript and Reason invites a lot of confusion. 但它在BuckleScript和Reason中实现和执行的方式引起了很多困惑。 It has unexpected behavior, encourages bad function design and unless one goes all in on it 4 , imposes a heavy cognitive tax when switching between the different pipe operators depending on what kind of function you're calling. 它有意想不到的行为,鼓励糟糕的功能设计,除非一个人全力以赴4 ,根据你所调用的功能,在不同的管道操作员之间切换时会产生沉重的认知税。

I would therefore recommend avoiding the pipe-first operator ( -> or |. ) and instead use pipe-forward ( |> ) with a placeholder argument (also exclusive to Reason) if you need to pipe to an "object"-first function, eg list |> List.map(...) |> Belt.List.keep(_, ...) . 因此,如果你需要管道到“对象” - 第一个函数,我建议避免使用管道优先运算符( ->|. ),而是使用带有占位符参数的管道转发( |> )(也不包括Reason) ,例如list |> List.map(...) |> Belt.List.keep(_, ...)


1 There are also some subtle differences with how this interacts with type inference, because types are inferred left-to-right, but it's not a clear benefit to either style IMO. 1这与类型推断的交互方式也存在一些微妙的差异,因为类型是从左到右推断出来的,但这对IMO的风格都没有明显的好处。

2 Because it requires syntactic transformation. 2因为它需要语法转换。 It can't be implemented as just an ordinary operator, unlike pipe-forward. 与管道前进不同,它不能仅作为普通的操作员实现。

3 For example, list |> List.map(...) -> Belt.List.keep(...) doesn't work as you'd expect 3例如, list |> List.map(...) -> Belt.List.keep(...) 无效,正如您所期望的那样

4 Which means being unable to use almost every library created before the pipe-first operator existed, because those were of course created with the original pipe-forward operator in mind. 4这意味着无法使用几乎所有在管道优先运算符存在之前创建的库,因为这些库当然是在考虑原始管道运算符的情况下创建的。 This effectively splits the ecosystem in two. 这有效地将生态系统分为两部分。

|> is usually called 'pipe-forward'. |>通常称为“管道前进”。 It's a helper function that's used in the wider OCaml community, not just ReasonML. 它是一个辅助功能,用于更广泛的OCaml社区,而不仅仅是ReasonML。 It 'injects' the argument on the left as the last argument into the function on the right: 它将左侧的参数“注入”作为右侧函数的最后一个参数:

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

-> is called 'pipe-first', and it's a new syntax sugar that injects the argument on the left into the first argument position of the function or data constructor on the right: ->被称为'pipe-first',它是一个新的语法糖,它将左边的参数注入到右边的函数数据构造函数的第一个参数位置:

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

Note that -> is specific to BuckleScript ie when compiling to JavaScript. 请注意->特定于BuckleScript,即编译为JavaScript时。 It's not available when compiling to native and is thus not portable. 在编译为本机时它不可用,因此不可移植。 More details here: https://reasonml.github.io/docs/en/pipe-first 更多细节: https//reasonml.github.io/docs/en/pipe-first

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

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