What?
The idea is to have a pipeline tap |:
operator, allowing to perform side effect inside a pipeline.
It returns the same value given in its input, while calling the given function on it.
(Using F# style pipeline)
const result = someData
|> parse
|> filter
|: log
|> format
Which would do the same as:
const result = someData
|> parse
|> filter
|> x => { log(x); return x }
|> format
Why?
The pipeline tap |:
operator can become very handy when logging or debugging inside a pipeline, or even anywhere else in the code.
For example, if we want, for debugging purposes, to quickly log the return value of the following function:
function randInt (min, max) {
return Math.floor( min + Math.random() * (max - min) );
}
we would have to refactor the code in order to be able to do so:
function randInt (min, max) {
const value = Math.floor( min + Math.random() * (max - min) );
console.log(value);
return value;
}
with the pipeline tap |:
operator, no refactoring would be needed, only appending the operator:
// Using F# syntax
function randInt (min, max) {
return Math.floor( min + Math.random() * (max - min) )
|: console.log;
}
// Using Hack syntax
function randInt (min, max) {
return Math.floor( min + Math.random() * (max - min) )
|: console.log("randInt:", ?);
}
Of course, as shown in the intro, it would also be very handy when used inside of a pipeline, so that the given input keeps flowing without being changed:
const sendMessage = () => chatBox
|> ?.value
|> formatMessage(?)
|: broadcastMessage(?)
|: appendMessageToHTML(?)
How?
This proposal depends on the final syntax chosen for pipelines (F# or Hack).
But in any case, the pipeline tap |:
operator can easily be designed to work in both syntax.
F# style:
expression |: handler
would desugar to
expression |> x => (handler(x), x)
Hack style:
expression |: handler(?)
would desugar to:
expression |> (handler(?), ?)
Operator precedence
This operator precedence is the same than the pipeline |>
operator, and is also left-to-right associative.
Pros and Cons
I'd like to discuss several pros and cons about this proposal:
Pros
Debugging heaven
You can almost log any values for debugging without having to heavily modify parts of your code for that:
- Declared variables:
const sum = a + b |: console.log
- Function return:
(a, b) => a + b |: console.log
- Single value in expression:
(a, b) => a + (b |: console.log)
- Pipelines
- ...
Syntax coherence
This point might be a little opinionated, but I do find the pipeline tap |:
operator quite elegant, and coherent with the rest of the language.
When looking at a pipeline flow, like:
const result = someData
|> parse
|> filter
|: log
|> format
the |
character visually continues the pipe along the normal |>
pipeline, but we can see at a glance that it is different. When the >
of the normal pipeline seems to indicate a movement forward, to indicate a change, the :
on the other hand, seems to be just presenting the data to the given handler, and nothing more. I feel that it looks quite descriptive about what it does, and there is no real confusion possible about which operator is doing what.
Cons
I'll need your help on this part, because I don't see a lot of cons coming with this proposal :)
One could say that the tap |:
operator could potentially lead to writing less clean code, as it would allow to call side-effect function anywhere in the code for any expression. But I personally think that most of its use outside a pipeline flow would be to create temporary debugging functions, and most of the "production-ready" uses are going to be wrote inside a pipeline flow, so I'm not worrying too much about this.
But this is only my opinion, please do not hesitate to give your own thoughts about it!