简体   繁体   中英

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. 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.

Currying

In a curried language, every function takes exactly one argument. 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:

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). 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. 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.

And then... BuckleScript

Then along comes Bob, who first authored BuckleScript in order to compile OCaml code to JavaScript. BuckleScript was adopted by Reason, and then Bob went on to create a standard library for BuckleScript called Belt . Belt ignores almost everything I've explained above by putting the main argument first . 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 .

Bob did recognize the importance of the pipe operator, however, so he created his own pipe-first operator, |. , which works only with 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

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. 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.

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(_, ...) .


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.

2 Because it requires syntactic transformation. 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

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. 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. 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:

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. It's not available when compiling to native and is thus not portable. More details here: https://reasonml.github.io/docs/en/pipe-first

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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