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

Add support for Hack(#) -style Pipeline Operator #43617

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

Pokute
Copy link

@Pokute Pokute commented Apr 9, 2021

Experiment PR adding Hack-style pipeline operator support to TypeScript. The Pipeline proposal is currently at stage 1 for EcmaScript. Pipeline operator proposal "introduces a new operator |> similar to F#, OCaml, Elixir, Elm, Julia, Hack, and LiveScript, as well as UNIX pipes and Haskell's &. It's a backwards-compatible way of streamlining chained function calls in a readable, functional manner, and provides a practical alternative to extending built-in prototypes."

There's additional Tacit Unary Function Application support through the |>> -operator which behaves exactly like minimal pipelines implementation taking the pipeline input as a sole parameter for the function.

'Pipelines!'
  |> #.length
  |> Math.pow(#, 2)
  |>> console.log; // 100

There's also a PR for minimal implementation pipeline operator.

Code status

This works, but might have some missing features. The TS transformer generates valid JS and there's working type inference. There might be some inconsistencies with specs, insufficient error messages and there's almost no tests. Most of failing tests are not included in this PR since Playground won't get generated with any failures and having faulty baselines is shady. I'm eagerly awaiting feedback, testing and even help with this PR. Even using and talking of these features will help advance them through TC39 proposal process!

Testing

Playground

To test it in your project, add following to your package.json:

{
    "devDependencies": {
        "typescript": "npm:@typescript-deploys/[email protected]"
    }
}

and then run npm install.

PR state

I used @rbuckton 's work on pipelineStage1 branch as a guide.

  • There is an associated issue in the Backlog milestone (required)
  • Code is up-to-date with the master branch
  • You've successfully run gulp runtests locally
  • There are new or updated unit tests validating the change

Why a draft PR for stage 1 proposal?

The TypeScript officially follows EcmaScript / stage 3 proposals. However, I'm opening this draft pull request despite it being only stage 1 so that we get a functional TypeScript environment to gather feedback from developers without having to build typescript itself. Having a playground and npm package will encourage more people to try this fun new feature out and will raise awareness.

Of course, I'm not expecting merging until the proposal reaches stage 3.

@typescript-bot typescript-bot added the For Uncommitted Bug PR for untriaged, rejected, closed or missing bug label Apr 9, 2021
@sandersn
Copy link
Member

sandersn commented Apr 9, 2021

@typescript-bot pack this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Apr 9, 2021

Heya @sandersn, I've started to run the tarball bundle task on this PR at 055dbe5. You can monitor the build here.

@typescript-bot
Copy link
Collaborator

typescript-bot commented Apr 9, 2021

Hey @sandersn, I've packed this into an installable tgz. You can install it for testing by referencing it in your package.json like so:

{
    "devDependencies": {
        "typescript": "https://typescript.visualstudio.com/cf7ac146-d525-443c-b23c-0d58337efebc/_apis/build/builds/100614/artifacts?artifactName=tgz&fileId=4DA38472A5E01EBFCD1A04F8504692D54C4DD31B0D5587760AF8836CFDF7554702&fileName=/typescript-4.3.0-insiders.20210409.tgz"
    }
}

and then running npm install.


There is also a playground for this build and an npm module you can use via "typescript": "npm:@typescript-deploys/[email protected]".;

@weswigham
Copy link
Member

Personal opinion: The hack-style pipeline operator is bad because it introduces a new context variable, like this, in the form of #. These don't nest in intuitive ways. In addition, the operator doesn't actually save much space for many usages. You could rewrite

'Pipelines!'
  |> #.length
  |> Math.pow(#, 2)
  |> console.log(#); // 100

as

var $$;
($$ = 'Pipelines!',
$$ = $$.length,
$$ = Math.pow($$, 2),
$$ = console.log($$)) // 100

without loss of functionality with very little comparable overhead. (If you use a 1 character variable name, it's a fixed 5 character overhead plus 2 characters per pipeline phase (extra whitespace and comma).

The functional pipeline operator, on the other hand, exists to deliver a point-free style that otherwise is impossible in JS - while some people may hate this style, that is what the true functional pipeline operator truly enables. (Plus, in the common functional style, each pipeline phase saves at least 4 characters, which is a much greater savings than the hack style). Moreover, the functionality of the "hack style" is replicatable (without implicit variables) using the functional style by embedding arrow functions into the pipeline, at a cost of 3-5 (depending on whitespace preferences) characters per pipeline phase which required adapted arguments. (Which shouldn't be too often, as if you find yourself adapting the same function's argument's repeatedly, in the case of either operator form, you should probably wrap it with an adapted form for clarity. For example, if Math.pow(#, 2) appeared more than once or twice, you should probably extract it into a single-argument square function.)

Thanks for coming to my rant. I'm obviously not on the TC39, so my opinion will have absolutely no bearing on which proposal advances.

@Pokute
Copy link
Author

Pokute commented Apr 15, 2021

Update: I've been refactoring the TS PRs for pipelines (both F# #38305 and hack #43617 ) during this week and the commits are a lot cleaner now. The current commit history for hack-style pipelines is just a mess since it builds upon the F# implementation. The newer one will use a common commit.

One big idea I came upon is to have different PipelineExpression types for both a method that itself calls a unary function (F#) and another (hack) that just replaces tokens with the "argument". After all, the way those two pipelines work is different despite the similarities. This will allow for easier support of both F#, hack and even smart pipeline styles at the same time.

I believe that whichever style of F# or hack pipelines will be the default, the next shop would be to add another pipelineish operator for the other one. This is relatively straightforward to implement in TypeScript, so I'm considering adding Tacit Unary Function Application syntax for this Hack-style PR to enable Math.random() |> 1 / # |>> Math.round;.

I've been rebasing my own repos against master, but I still haven't had time to fix the (Missing) errors in Hack-style pipelines. Hopefully I get new versions out before the end of the week.

@Pokute Pokute force-pushed the pipeline-operator-hack-hash branch from 055dbe5 to e1c9d1f Compare April 17, 2021 21:45
@orta
Copy link
Contributor

orta commented Apr 18, 2021

@typescript-bot pack this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Apr 18, 2021

Heya @orta, I've started to run the tarball bundle task on this PR at e1c9d1f. You can monitor the build here.

@typescript-bot
Copy link
Collaborator

typescript-bot commented Apr 18, 2021

Hey @orta, I've packed this into an installable tgz. You can install it for testing by referencing it in your package.json like so:

{
    "devDependencies": {
        "typescript": "https://typescript.visualstudio.com/cf7ac146-d525-443c-b23c-0d58337efebc/_apis/build/builds/101227/artifacts?artifactName=tgz&fileId=4BEC488C0D1690EC3672E3929BA5609612A00993D3023D1FCB52E34A6B983A5702&fileName=/typescript-4.3.0-insiders.20210418.tgz"
    }
}

and then running npm install.


There is also a playground for this build and an npm module you can use via "typescript": "npm:@typescript-deploys/[email protected]".;

@kawazoe
Copy link

kawazoe commented May 16, 2021

After giving this PR a small trial run, I noticed a pretty big roadblock toward your adoption goal.

The little bits of the feature I could try felt natural and worked correctly in a sandbox but, to get a proper feel for it, I think I would really need to use it in an actual project. Unfortunately, while I would have a great VueJs candidate to test this on, I had to give up on eslint entirely for it to work. With linting enabled in the project, vue cli basically explodes during compilation.

For me, until we can get some experimental eslint support for TS 4.3 and the pipeline operator syntax itself (via @typescript-eslint/parser), I don't think there will be much adoption outside of sandbox testing. There is just too much value in a tool like eslint to warrant ditching it to test a proposal feature in a real world scenario.

I will definitely be back on board the second a version that doesn't crash with this PR gets published, even if it means it only ignores everything in pipelines.

@ghost
Copy link

ghost commented Jun 6, 2021

I've been using this PR for a little while on a Discord bot refactor/TypeScript migration project that will be open-source once completed. I greatly enjoy using it to avoid nesting parentheses or avoid declaring variables where function names already communicate intent. I miss it immediately when going back to company code.

Here's a file using small pipelines from my project, it's a Discord message event handler. There're pipelines on lines 30, 42 and 47. It's my first project using TypeScript, so there might be weird stuff. 😖

I've been mostly using the |>> variant because I enjoy point-free style. (If you add partial application to the F# variant, I'll migrate to that one.) It goes very well with curried functions like Ramda's.

Minor feedback on the implementation: the non-null assertion ! post-fix operator can't be used on the # placeholder token. You get [tsserver 18026] [E] '#!' can only be used at the start of a file.

@Lonli-Lokli
Copy link

Now at Stage 2

https://twitter.com/leobalter/status/1432741880421257217

@noppa
Copy link

noppa commented Sep 1, 2021

@Lonli-Lokli The tweet makes it sound like the proposal already advanced but I don't think it did yet, it was "just" presented today and seeking stage 2 (which is big news too of course). I'm making assumptions based on very little data here but for example "BigInt Math" was scheduled to be presented after pipelines and its advancement has already been updated, unlike pipeline's.

Edit: I guess I was wrong 🙃 tc39/proposals@f410ced

@kiprasmel
Copy link

kiprasmel commented Sep 11, 2021

Hey everyone. Let's not rush this.

tc39/proposal-pipeline-operator#91 (comment)

@nicolo-ribaudo
Copy link

@Pokute It looks like there is a transform bug: 1 |> (# |> #) should be rewritten as

var pipelineHackPlaceholder_1, pipelineHackPlaceholder_2;
(pipelineHackPlaceholder_1 = 1, ((pipelineHackPlaceholder_2 = pipelineHackPlaceholder_1, pipelineHackPlaceholder_2)));

and not as

var pipelineHackPlaceholder_1, pipelineHackPlaceholder_2;
(pipelineHackPlaceholder_1 = 1, ((pipelineHackPlaceholder_2 = #, pipelineHackPlaceholder_2)));

@Pokute
Copy link
Author

Pokute commented Sep 23, 2021

I don't think posting Hack vs. F# arguments to these TypeScript pull requests has any productive outcome. There's no good reason why we should fragment the that discussion further by having parallel discussions here, where vast majority of people invested into pipeline operator will not see it.

These kinds of comments, or listing community advocates for one proposal or another have no impact whatsoever on whether this specific implementation of pipeline operator for TypeScript is correct or not.

@ken-okabe
Copy link

I made an issue:
Inconsistency of type inference of Hack-style pipeline-operator proposal (Stage2) #46101
also an article:
TC39/proposal-pipeline-operator Hack-style |> hijacks Grouping operator ( )
https://dev.to/stken2050/the-current-tc39-proposal-pipeline-operator-hack-style-hijacks-grouping-operator-1dam

@micheal-hill
Copy link

@stken2050 can you please stop spamming this and the F#-style PR threads?

@ken-okabe
Copy link

ken-okabe commented Sep 27, 2021

@micheal-hill Thanks for your insight, and I've deleted the previous 4 comments by myself.

@VictorGaiva
Copy link

VictorGaiva commented Oct 24, 2021

Can we make this PR up to date with 4.4? The spec is settled, so this PR could move forward.

@Pokute
Copy link
Author

Pokute commented Nov 28, 2021

I have a new version updated against new main ready, that fixes bug reported by nicolo-ribaudo and a couple of other bugs too.

Unfortunately there's no good point in pushing it currently as the master is broken. I'll push it when #46930 is fixed.

@VictorGaiva
Copy link

Many thanks. Can't wait to test it out!

@Pokute Pokute force-pushed the pipeline-operator-hack-hash branch from e1c9d1f to a0fdb2a Compare November 29, 2021 14:31
@orta
Copy link
Contributor

orta commented Nov 29, 2021

@typescript-bot pack this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Nov 29, 2021

Heya @orta, I've started to run the tarball bundle task on this PR at a0fdb2a. You can monitor the build here.

@typescript-bot
Copy link
Collaborator

typescript-bot commented Nov 29, 2021

Hey @orta, I've packed this into an installable tgz. You can install it for testing by referencing it in your package.json like so:

{
    "devDependencies": {
        "typescript": "https://typescript.visualstudio.com/cf7ac146-d525-443c-b23c-0d58337efebc/_apis/build/builds/115513/artifacts?artifactName=tgz&fileId=C343958DBF726221B05919B84052CF22BD28EC8DF68CB18C89CA7435869B5DBB02&fileName=/typescript-4.6.0-insiders.20211129.tgz"
    }
}

and then running npm install.


There is also a playground for this build and an npm module you can use via "typescript": "npm:@typescript-deploys/[email protected]".;

@VictorGaiva
Copy link

Can this be packed with [email protected] ?

@Pokute
Copy link
Author

Pokute commented Nov 30, 2021

Can this be packed with [email protected] ?

Ergonomically, that would require force pushing a version that's rebased on an older version. That would be practically undoing some work and it the repo would most probaly not be mergeable to current main branch. It's likely that packing is not possible unless I change the target branch to the 4.5 tag and for that I would have to create a new PR etc. A ton of work and more noise.

@VictorGaiva, what are the reasons you want one for version 4.5 instead of the current main branch? I don't quite understand the reasonings and with them I might be able to help more.

@hd-o
Copy link

hd-o commented Feb 10, 2022

  1. Is this ok, #playground, or have I gone too F#? 🤠
  2. @Pokute Thanks for the work on this! 🎉
  3. I have a small issue with VS Code (stable, and insiders), where the TS language server hangs on "starting" after I select the project's TS version npm:@typescript-deploys/[email protected]. Anyone else? It works fine on Atom 🤔

@Pokute
Copy link
Author

Pokute commented Feb 12, 2022

I don't think that there are established hack vs. F# styles yet. The pipeline's main attraction is normalising the direction of data flow which is present in your code. Only through long-term experimentation can we build some best-practices.

  • @Pokute Thanks for the work on this! 🎉

Thank you! I appreciate testing pipelines out and the feedback!

Interesting. Do extensions and tsconfig.json matter here? I don't think I've had problems recently though I haven't used my own TS build much recently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
For Uncommitted Bug PR for untriaged, rejected, closed or missing bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.