Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Starting to use a pipeline operator now #148

Closed
peoro opened this issue Feb 6, 2019 · 14 comments
Closed

Starting to use a pipeline operator now #148

peoro opened this issue Feb 6, 2019 · 14 comments

Comments

@peoro
Copy link

@peoro peoro commented Feb 6, 2019

I've been waiting for the pipeline operator (and/or the bind operator) for a long time, and finally decided to become a bit more proactive.

After reading tons of discussions I have a few questions about the current status and would like to participate in the discussion.

About the current status

Two very conflicting syntaxes seem to be fighting for their place in the standard: F# Pipelines and Smart Pipelines.
This prevents me from adopting now a pipeline operator through transpilation: I'd have to refactor all my code if the syntax I didn't pick gets chosen...

About F# Pipelines

It's obviously kind of awkward to use this syntax with functions that take multiple arguments. And that's arguably the most common use case: what's the point of this syntax, then?

Would we be supposed to curry everything? Wouldn't that be uncomfortable and degrade performances?

The Partial Application expression would make this syntax a lot easier to use (and easier to optimize). Shouldn't we consider Partial Application as a requirement for F# Pipelines?

About Smart Pipelines

This syntax is obviously easier to use right now.
What I don't love of it, is that it's less KISS than F#Pipelines+PartialApplication: has more corner cases and it's less modular.

Besides, I'm afraid that we could forget about Partial Application if this syntax were chosen. That'd be a pity, since Partial Application would be handy even on its own, outside of pipelines.


I'd pick F#Pipelines+PartialApplication over SmartPipelines, but the latter over F#Pipelines only.

Am I aligned with the current ideas of the committee?

I'm a bit bugged by the fact that there seems to be no transpiler for Partial Application, which would prevent me from start using my preferred solution right now...

My thoughts

The reason why I'm desperate for a pipeline operator, is that way way too often I need to call a method-like free function that operates on it's first argument. So so many libraries are exporting functions that work that way, and it comes very natural to write functions like that.

Currently that's absolutely my prime use case.
And this is the prime case for the current mainstream JavaScript ecosystem, I'm convinced.

I've read many comments saying that it's not enough, that when coding in a strongly functional way what I just described is not enough.
I don't know much about it: would anybody be able to provide some real-world examples? I found many among this github's issues, but wasn't quite convinced by them.

Proposal: opipe operator

I decided to implement a new operator that I can start using right now.
My goals were:

  • Using a new, unused operator, so that it's somehow future proof.
  • Having it as simple as possible.

I don't really care about it joining the standard: it's fine if it'll always need transpilation. It's just something that I want to start using in my code now. Besides, once any pipeline operator gets standardized it should be very simple to write a tool to automatically replace opipe with the pipeline.

I ended up implementing the opipe operator, :|:

a :| b(...args)  b(a, ...args)

So you can do:

const _ = lodash;
const ten = [1,2,3,4]
    :| _.filter( n=>n%2 ) // [1,3]
    :| _.map( n=>n**2 )   // [1,9]
    :| _.sum();           // 10

This semantics is nothing new: it's something that has already been plenty discussed (and discarded) and even previously implemented, although that implementation uses ::, already taken by the bind operator.

I chose the symbol :| as it's a mixture of bind a pipe, and called it opipe as it's used to pipe an object through method-like free functions.

If anybody is interested in playing with it:
NPM: https://www.npmjs.com/package/babel-plugin-transform-opipe
GitHub: https://github.com/peoro/babel-plugin-transform-opipe

If you think that using this operator is a bad idea, I'd be very happy to hear it before start using it extensively.

@ljharb
Copy link
Member

@ljharb ljharb commented Feb 6, 2019

what's _.filter here? is the _ meant to be a placeholder?

@peoro
Copy link
Author

@peoro peoro commented Feb 6, 2019

what's _.filter here? is the _ meant to be a placeholder?

No, _ is lodash (or underscore).
Thought that it was clear enough, sorry, editing the original post.

@zenparsing
Copy link
Member

@zenparsing zenparsing commented Feb 6, 2019

@peoro See #143 (comment) for (I think) a similar proposal. Is that what you have in mind?

@ljharb
Copy link
Member

@ljharb ljharb commented Feb 6, 2019

@peoro in that case, i'm confused, is the thing being piped being magically/invisibly inserted as the first argument to the next function call?

@peoro
Copy link
Author

@peoro peoro commented Feb 6, 2019

@zenparsing, yes. Also tc39/proposal-bind-operator#24 (comment) and #55 discuss the same semantics.

@peoro
Copy link
Author

@peoro peoro commented Feb 6, 2019

@ljharb yes, exactly. a :| b(...args) becomes b(a, ...args).

@ljharb
Copy link
Member

@ljharb ljharb commented Feb 6, 2019

@peoro i think it'd be pretty important to avoid confusion to require an explicit placeholder rather than magically inserting an argument somewhere

@peoro
Copy link
Author

@peoro peoro commented Feb 6, 2019

@ljharb, my goal was having an operator as simple as possible and the current :| is way simpler like this than if it supported a placeholder. It's simpler both syntactically and semantically.

I'm mostly gonna use :| for my self, I don't mind if it will never be considered for standardization (the semantics I proposed has already been extensively discussed and discarded, for the record) or if it's confusing to read for strangers exploring my code.
Once |> gets standardized, I'm planning to automatically refactor :| into |> (and I believe that the simplicity of the current syntax/semantics will make this even easier).

In any case, I didn't mean to only focus on :|. That's an operator I wrote for myself and that I'm happy to share with anybody who might find it useful.
I'm very interested in understanding more about the current status of the pipeline operator and the various design choices.

@mAAdhaTTah
Copy link
Collaborator

@mAAdhaTTah mAAdhaTTah commented Feb 6, 2019

Thank you for sharing your experience so far! My thoughts / comments are below:

Two very conflicting syntaxes seem to be fighting for their place in the standard: F# Pipelines and Smart Pipelines.

Yeah, we're currently looking at these 2 proposals, as well as some discussions around the bind operator as well as the Elixir-style pipe @zenparsing mentioned above. So even more than just 2!

This prevents me from adopting now a pipeline operator through transpilation: I'd have to refactor all my code if the syntax I didn't pick gets chosen...

Yeah... Stage 1 features shouldn't be used in production without accepting either a lot of potential refactoring or the possibility of the feature getting dropped completely (or transpiling forever).

It's obviously kind of awkward to use this syntax with functions that take multiple arguments. And that's arguably the most common use case: what's the point of this syntax, then?

Would we be supposed to curry everything? Wouldn't that be uncomfortable and degrade performances?

If by "currying", you mean with arrow functions, yes, that's the intended usage:

x |> (x => add(x, 2))

(We are looking at potentially dropping the parens around the arrow functions but they're required for now.)

Personally, I don't find that a problem, but that's why we're looking to implement in Babel, to get feedback as to whether it's more or less of an issue than we think.

The Partial Application expression would make this syntax a lot easier to use (and easier to optimize). Shouldn't we consider Partial Application as a requirement for F# Pipelines?

I'm hesitant to bind two proposals together like that. I don't want the success of pipeline to be contingent on partial application, and both proposals should be given enough time & space to come out fully baked on their own, without requiring a dependency of one on the other.

Besides, I'm afraid that we could forget about Partial Application if this syntax were chosen. That'd be a pity, since Partial Application would be handy even on its own, outside of pipelines.

Yes & no; Smart pipelines has a number of follow-on proposals to extend its placeholders for usage in simpler pipes that would allow you to use it in many of the places you would use partial application. See here as an example.

I'm a bit bugged by the fact that there seems to be no transpiler for Partial Application, which would prevent me from start using my preferred solution right now...

There isn't transpilation for F# either; I'm assuming you've been using proposal: "minimal"?

@peoro
Copy link
Author

@peoro peoro commented Feb 6, 2019

If by "currying", you mean with arrow functions, yes, that's the intended usage:

x |> (x => add(x, 2))

(We are looking at potentially dropping the parens around the arrow functions but they're required for now.)

Personally, I don't find that a problem, but that's why we're looking to implement in Babel, to get feedback as to whether it's more or less of an issue than we think.

Yes, by "currying" I meant either using an anonymous function, or some kind of bind function.

I have not tried to actually rewrite pieces of code using the F# syntax and arrow functions, but at glance it doesn't seem ideal. I asume that F#+PartialApplication, Smart Pipelines (and even :|) are all objectively more ergonomics than F#+ArrowFunctions.
I believe that the main drive for a pipeline operator is functions similar to the lodash's ones (I've been calling them method-like as they would make sense as member functions of some prototypes, and sometimes they are).
I find it weird that a new syntax, added primarily to cover that use case, still requires some boilerplate to do the very same thing it's supposed to simplify.

That's what made me question the F# proposal: what use cases is it covering, and how big is each use case?

Thanks for all the answers, they cleared up a bunch of doubts.

@nicolo-ribaudo
Copy link
Member

@nicolo-ribaudo nicolo-ribaudo commented Feb 6, 2019

This prevents me from adopting now a pipeline operator through transpilation: I'd have to refactor all my code if the syntax I didn't pick gets chosen...

Given how many people would need to refactor their syntax, I think that we could create a tool similar to https://github.com/nicolo-ribaudo/legacy-decorators-migration-utility: one command, and all your project is upgraded to the final syntax.

I'm a bit bugged by the fact that there seems to be no transpiler for Partial Application, which would prevent me from start using my preferred solution right now...

We (Babel) are currently working on it 😉

@mAAdhaTTah
Copy link
Collaborator

@mAAdhaTTah mAAdhaTTah commented Feb 6, 2019

I asume that F#+PartialApplication, Smart Pipelines (and even :|) are all objectively more ergonomics than F#+ArrowFunctions.

This is probably true, but ergonomics come at the cost of complexity for all of those. F#+Arrow introduce a simpler construct and builds on syntax we already know.

That's what made me question the F# proposal: what use cases is it covering, and how big is each use case?

FP-style programming relies heavily on currying & unary functions. Also the importable operators style, a-la RxJS's pipe, would take the object to operate on as a future argument:

import { from, map } from 'rxjs';

from([1,2,3]) |> map(x => x * 2);

This type of unary composition is pretty common in the React world for composing up components as well.

@mlanza
Copy link

@mlanza mlanza commented Dec 23, 2020

I agree partial application syntax should go hand in hand with the F# pipeline proposal, but I like that they are separate. It just so happens they are complementary.

@tabatkins
Copy link
Collaborator

@tabatkins tabatkins commented Sep 8, 2021

Closing this issue, as the proposal has advanced to stage 2 with Hack-style syntax. See https://babeljs.io/blog/2021/07/26/7.15.0#hack-style-pipeline-operator-support-13191httpsgithubcombabelbabelpull13191-13416httpsgithubcombabelbabelpull13416 for the matching Babel plugin.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 12, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants