Skip to content

Latest commit

 

History

History
247 lines (172 loc) · 5.84 KB

File metadata and controls

247 lines (172 loc) · 5.84 KB
titledescriptioncanonical
Pipe
The Pipe operator (->)
/docs/manual/latest/pipe

Pipe

ReScript provides a tiny but surprisingly useful operator ->, called the "pipe", that allows you to "flip" your code inside-out. a(b) becomes b->a. It's a simple piece of syntax that doesn't have any runtime cost.

Why would you use it? Imagine you have the following:

<CodeTab labels={["ReScript", "JS Output"]}>

validateAge(getAge(parseData(person)))
validateAge(getAge(parseData(person)));

This is slightly hard to read, since you need to read the code from the innermost part, to the outer parts. Use pipe to streamline it:

<CodeTab labels={["ReScript", "JS Output"]}>

person->parseData->getAge->validateAge
validateAge(getAge(parseData(person)));

Basically, parseData(person) is transformed into person->parseData, and getAge(person->parseData) is transformed into person->parseData->getAge, etc.

This works when the function takes more than one argument too.

<CodeTab labels={["ReScript", "JS Output"]}>

a(one, two, three)
a(one,two,three);

is the same as

<CodeTab labels={["ReScript", "JS Output"]}>

one->a(two, three)
a(one,two,three);

This also works with labeled arguments.

Pipes are used to emulate object-oriented programming, e.g. what's myStudent.getName in other languages like Java would be myStudent->getName in ReScript (aka getName(myStudent)). This allows us to have the readability of the good parts of OOP without its downside of dragging in a huge class system just to call a function on a piece of data.

Tips & Tricks

Do not to abuse pipes; they're a means to an end. Inexperienced engineers sometimes shape a library's API to take advantage of the pipe. This is backward.

JS Method Chaining

This section requires understanding of our binding API.

JavaScript's APIs are often attached to objects, and often chainable, like so:

constresult=[1,2,3].map(a=>a+1).filter(a=>a%2===0);asyncRequest().setWaitDuration(4000).send();

Assuming we don't need the chaining behavior above, we'd bind to each case this using bs.send from the aforementioned binding API page:

<CodeTab labels={["ReScript", "JS Output"]}>

@bs.sendexternalmap: (array<'a>, 'a=>'b) =>array<'b> ="map" @bs.sendexternalfilter: (array<'a>, 'a=>bool) =>array<'a> ="filter"typerequest @bs.valexternalasyncRequest: unit=>request="asyncRequest" @bs.sendexternalsetWaitDuration: (request, int) =>request="setWaitDuration" @bs.sendexternalsend: request=>unit="send"
// Empty output

You'd use them like this:

<CodeTab labels={["ReScript", "JS Output"]}>

letresult=Js.Array2.filter( Js.Array2.map([1, 2, 3], a=>a+1), a=>mod(a, 2) ==0 ) send(setWaitDuration(asyncRequest(), 4000))
varresult=[1,2,3].map(function(a){returna+1|0;}).filter(function(a){returna%2===0;});asyncRequest().setWaitDuration(4000).send();

This looks much worse than the JS counterpart! Clean it up visually with pipe:

<CodeTab labels={["ReScript", "JS Output"]}>

letresult= [1, 2, 3] ->map(a=>a+1) ->filter(a=>mod(a, 2) ==0) asyncRequest()->setWaitDuration(4000)->send
varresult=[1,2,3].map(function(a){returna+1|0;}).filter(function(a){returna%2===0;});asyncRequest().setWaitDuration(4000).send();

Pipe Into Variants

You can pipe into a variant's constructor as if it was a function:

<CodeTab labels={["ReScript", "JS Output"]}>

letresult=name->preprocess->Some
varresult=preprocess(name);

We turn this into:

<CodeTab labels={["ReScript", "JS Output"]}>

letresult=Some(preprocess(name))
varresult=preprocess(name);

Note that using a variant constructor as a function wouldn't work anywhere else beside here.

Pipe Placeholders

A placeholder is written as an underscore and it tells ReScript that you want to fill in an argument of a function later. These two have equivalent meaning:

letaddTo7= (x) =>add3(3, x, 4) letaddTo7=add3(3, _, 4)

Sometimes you don't want to pipe the value you have into the first position. In these cases you can mark a placeholder value to show which argument you would like to pipe into.

Let's say you have a function namePerson, which takes a person then a name argument. If you are transforming a person then pipe will work as-is:

<CodeTab labels={["ReScript", "JS Output"]}>

makePerson(~age=47, ()) ->namePerson("Jane")
namePerson(makePerson(47),"Jane");

If you have a name that you want to apply to a person object, you can use a placeholder:

<CodeTab labels={["ReScript", "JS Output"]}>

getName(input) ->namePerson(personDetails, _)
var__x=getName(input);namePerson(personDetails,__x);

This allows you to pipe into any positional argument. It also works for named arguments:

<CodeTab labels={["ReScript", "JS Output"]}>

getName(input) ->namePerson(~person=personDetails, ~name=_)
var__x=getName(input);namePerson(personDetails,__x);

Triangle Pipe (Deprecated)

You might see usages of another pipe, |>, in some codebases. These are deprecated.

Unlike -> pipe, the |> pipe puts the subject as the last (not first) argument of the function. a |> f(b) turns into f(b, a).

For a more thorough discussion on the rationale and differences between the two operators, please refer to the Data-first and Data-last comparison by Javier Chávarri

close