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

Support proposed ES Next "|>" pipeline operator #17718

Open
graingert opened this issue Aug 10, 2017 · 79 comments
Open

Support proposed ES Next "|>" pipeline operator #17718

graingert opened this issue Aug 10, 2017 · 79 comments

Comments

@graingert
Copy link

@graingert graingert commented Aug 10, 2017

No description provided.

@AlexGalays
Copy link

@AlexGalays AlexGalays commented Aug 10, 2017

My favourite proposal ever :( Nowadays, we can really write this free programs.

Loading

@aluanhaddad
Copy link
Contributor

@aluanhaddad aluanhaddad commented Aug 10, 2017

For reference, the TC39 proposal: https://github.com/tc39/proposal-pipeline-operator

Loading

@Pajn
Copy link

@Pajn Pajn commented Aug 15, 2017

Not that the proposal is not even at stage 0 yet. If it ever is added to the language semantics and other details will likely change.

Loading

@AlexGalays
Copy link

@AlexGalays AlexGalays commented Aug 15, 2017

This would be a first I think (beside some oldies like Enum and the module system) but could typescript implementing this give it more visibility and boost demand for it in the rest of the ecma ecosystem?

Loading

@PublicParadise
Copy link

@PublicParadise PublicParadise commented Aug 15, 2017

Just wanted to share a workaround for the missing pipeline operator inspired by https://vanslaars.io/post/create-pipe-function/...

SyncPipe with synchronous reduction

// SyncPipe with synchronous reduction
type SyncPipeMapper<T, U> = (data: T | U) => U;
type SyncPipeReducer<T, U> = (f: SyncPipeMapper<T, U>, g: SyncPipeMapper<T, U>) => SyncPipeMapper<T, U>;
type SyncPipe<T, U> = (...fns: SyncPipeMapper<T, U>[]) => SyncPipeMapper<T, U>;
function createSyncPipe<T, U>(): SyncPipe<T, U> {
    const syncPipe: SyncPipeReducer<T, U> = (f: SyncPipeMapper<T, U>, g: SyncPipeMapper<T, U>) => (data: T) => g(f(data));
    return (...fns: SyncPipeMapper<T, U>[]): SyncPipeMapper<T, U> => fns.reduce(syncPipe);
}

// Example:
function testSyncPipe(num: number): number {
    const addOne: SyncPipeMapper<number, number> = (data: number): number => {
        return data + 1;
    }
    const syncPipe: SyncPipe<number, number> = createSyncPipe();
    const syncWaterfall: SyncPipeMapper<number, number> = syncPipe(
        addOne,
        addOne,
        addOne,
    );

    // Does the equivalent of num+3
    const lastnumber: number = syncWaterfall(num);
    return lastnumber;
}

AsyncPipe with asynchronous reduction

// AsyncPipe with asynchronous reduction
type AsyncPipeMapper<T, U> = (data: T | U) => Promise<U>;
type AsyncPipeReducer<T, U> = (f: AsyncPipeMapper<T, U>, g: AsyncPipeMapper<T, U>) => AsyncPipeMapper<T, U>;
type AsyncPipe<T, U> = (...fns: AsyncPipeMapper<T, U>[]) => AsyncPipeMapper<T, U>;
function createAsyncPipe<T, U>(): AsyncPipe<T, U> {
    const asyncPipe: AsyncPipeReducer<T, U> = (f: AsyncPipeMapper<T, U>, g: AsyncPipeMapper<T, U>) => async (data: T) => g(await f(data));
    return (...fns: AsyncPipeMapper<T, U>[]): AsyncPipeMapper<T, U> => fns.reduce(asyncPipe);
}

// Example:
async function testAsyncPipe(num: number): Promise<number> {
    const addOne: AsyncPipeMapper<number, number> = async (data: number): Promise<number> => {
        return data + 1;
    }
    const asyncPipe: AsyncPipe<number, number> = createAsyncPipe();
    const asyncWaterfall: AsyncPipeMapper<number, number> = asyncPipe(
        addOne,
        addOne,
        addOne,
    );

    // Does the equivalent of num+3
    const lastnumber: number = await asyncWaterfall(num);
    return lastnumber;
}

Pipe with asynchronous reduction (simplified)

I use this one most of the time:

// Pipes with asynchronous reduction
type PipeMapper<T> = (data: T) => Promise<T>;
type PipeReducer<T> = (f: PipeMapper<T>, g: PipeMapper<T>) => PipeMapper<T>;
type Pipe<T> = (...fns: PipeMapper<T>[]) => PipeMapper<T>;
function createPipe<T>(): Pipe<T> {
    const pipePipe: PipeReducer<T> = (f: PipeMapper<T>, g: PipeMapper<T>) => async (data: T) => g(await f(data));
    return (...fns: PipeMapper<T>[]): PipeMapper<T> => fns.reduce(pipePipe);
}

// Example:
async function testPipe(num: number): Promise<number> {
    const addOne: PipeMapper<number> = async (data: number): Promise<number> => {
        return data + 1;
    }
    const pipe: Pipe<number> = createPipe();
    const waterfall: PipeMapper<number> = pipe(
        addOne,
        addOne,
        addOne,
    );
    // Does the equivalent of num+3
    const lastnumber: number = await waterfall(num);
    return lastnumber;
}

I hope you will find this helpful!

Loading

@AlexGalays
Copy link

@AlexGalays AlexGalays commented Aug 16, 2017

@PublicParadise way too much boilerplate :p

Loading

@aluanhaddad
Copy link
Contributor

@aluanhaddad aluanhaddad commented Aug 16, 2017

While I would definitely like to see some variant of this operator in the language the perceived need for it comes from two different limitations of ECMAScript as it currently stands.

The first is very hard to work around or even address in the language: the inability to extend built in objects in a hygienic manner.

The second however does not need language level support at all and could in fact be rectified: the standard library can kindly be called anemic.

Maximally Minimal is a complete failure.

Why does it take months and months of argument to get Array.prototype.flatMap in the language?

That's one method and it should have been there from the beginning and it should be obvious that it should be added.

Maybe Array.prototype will have a groupBy method in 6 years.

Loading

@KiaraGrouwstra
Copy link
Contributor

@KiaraGrouwstra KiaraGrouwstra commented Sep 23, 2017

By now this has a few babel implementations, which will hopefully help along the TC39 proposal:

Loading

@ddimaria
Copy link

@ddimaria ddimaria commented Sep 26, 2017

It's in stage 1 now

Loading

@galkowskit
Copy link

@galkowskit galkowskit commented Sep 29, 2017

So, any chances for this beauty getting into TS? It would be in-line with F#. <3

Loading

@kitsonk
Copy link
Contributor

@kitsonk kitsonk commented Sep 29, 2017

While there are exceptions, when a proposal is important to TypeScript and types, proposal are not typically implemented until they reach TC39 Stage 3 in TypeScript, as they are not stable enough to ensure that there won't be significant breakage and regressions.

While none of the core team have commented on this issue yet, it wouldn't be in my opinion important enough to be considered for implementation before Stage 3. The best focus is to support the champion and proposal at TC39.

Loading

@garkin
Copy link

@garkin garkin commented Oct 26, 2017

If only TS had an option to just pipe this operator through to allow a babel with plugins to deal with it.
Or had own syntax plugins, like post-css do. Several years waiting for a primitive operator is just too much.

Loading

@KiaraGrouwstra
Copy link
Contributor

@KiaraGrouwstra KiaraGrouwstra commented Oct 26, 2017

@garkin: The challenge here is TS needs to understand the code to do its job of providing type safety, which doesn't combine well with random code it doesn't understand. Unless it were to get macros (#4892), in which case it'd just compile to code it does understand. But I wouldn't expect that on the roadmap quite yet, as quite a few bits of the standard library are still challenging to type atm.

Loading

@graingert
Copy link
Author

@graingert graingert commented Oct 26, 2017

Loading

@AlexGalays
Copy link

@AlexGalays AlexGalays commented Oct 26, 2017

Now that Babel understands typescript you could run it through Babel then
typescript

Twice the build time though :p

Loading

@graingert
Copy link
Author

@graingert graingert commented Oct 26, 2017

Loading

@garkin
Copy link

@garkin garkin commented Oct 27, 2017

@graingert: It's a nice option to have, i'll investigate this.
Unfortunately, it won't work with typescript Language Service API which is used by VisualStudioCode, Webstorm and other IDEs.

Loading

@kbtz
Copy link

@kbtz kbtz commented Oct 27, 2017

Regarding "TS plugins" one could easily achieve the desired result with, let's say, a simple (pre)transpiler for the pipe operator that understands TS syntax and produces it's strong typed equivalent of the statement. It would compile just fine with type checking and whatnot.

A webpack configuration for that could look something like this:

module: {
		rules: [
			{ test: /\.ts$/, loader: 'ts-pipe-operator', enforce: 'pre' },
			{ test: /\.ts$/, loader: 'ts-loader' },
			...
        ]
 }

The only challenge, as pointed by @garkin, is that TS service wouldn't be able to correlate transpiled parts to the original source file, then IDEs that uses the service wouldn't work properly even if they already recognize the operator (ES Next syntax enabled or something).

Perhaps if we create a NFR (or maybe there's already one?) for TS service to support cumulative source maps to be applied between the source file and the transpiled result that is feed to the compiler, this and other plugins would be possible without affecting the rest of the community and, most importantly, without adding more complexity for the core team to deal with.


Also, I can't tell how much #13940 is related to this but its apparently a good start towards more complex plugins. However it seems to me that the source map approach still much simpler alternative since a minimalistic (pre)transpiler wouldn't need the project context for most cases as it would be fairly easy to just extract the type notation blocks (if any) from the statement raw text and then rewrite it in a way that control flow will be able to imply the specific I/O types for the transpiled part.


Last but not least, could anyone please point me out to the official thread (if any) regarding this kind of plugins?

Loading

@garkin
Copy link

@garkin garkin commented Oct 27, 2017

I need to say i realy appreciate calm way of introducing new proposals and prefer the monolithic Typescript and LessCSS tooling much more, than Flow+Babel and Post-CSS special plugins olympics.

It's a customizabilty and a speed of getting new features in cost of bloating fragmentation and an area of expertise.

The pipe operator is just like an entry drug to the functional world, it makes me say and wish a weird things.

So, there are #14419 and even some useful practical implications already. Should not be hard to integrate those with ts-loader.

tsconfig.json transformers integration (and so Language Service API, not just customized tsc) #14654 was declined in short term.
#11976 is discussing a Language Service plugins, which looks like a linting only tools.
#16607 proposing extension of those plugins to the transformers.

Loading

@felixfbecker
Copy link

@felixfbecker felixfbecker commented Nov 1, 2017

@PublicParadise or just use lodash's flow or Rambda's pipe?

anyway, this would be so awesome to see to be supported in TS. I love the functional patterns JS supports (especially with TS' type inference), but some patterns don't read very nice. This would be huge as big TS libraries like RxJS and IxJS are moving towards point-free functional composition over prototype extension/inheritance, it makes for way better tree shaking and support for custom operators.

Loading

@AlexGalays
Copy link

@AlexGalays AlexGalays commented Nov 2, 2017

@felixfbecker You mean ramda's pipe? I need to try again but historically, ramda being a JS-first lib, it's very dynamic and hard to type (like lodash), compounded by the fact TS used to have a lot of trouble inferring from function return values (it may have been fixed recently, but not sure)
I don't use lodash as it's poorly designed, mixing mutable and immutable functions in one big namespace.

Loading

@felixfbecker
Copy link

@felixfbecker felixfbecker commented Nov 2, 2017

Loading

@KiaraGrouwstra
Copy link
Contributor

@KiaraGrouwstra KiaraGrouwstra commented Nov 2, 2017

It actually works decently well if your functions and chains are not super crazy

Let me qualify 'not super crazy' there: things break down if your functions have generics (see typed-typings/npm-ramda#86), e.g. R.pipe(R.identity).

Loading

@kitsonk
Copy link
Contributor

@kitsonk kitsonk commented Nov 2, 2017

Also, let's be clear, the proposal is Stage 1. The core team are getting even more shy about introducing things before Stage 3. Decorators are part of the example. Even though they were marked as experimental, we all went ahead and enabled that flag and wrote all of our production code using them. The proposal now has bounced around and there are some fundamental breaking changes in the syntax and semantics that are going to mean we are all going to have to refactor our code which puts the core team in a tight situation, because if they only support the final syntax than everyone is broken the day they release it, or if they keep the legacy stuff, other changes in the compiler could make it challenging to support the two syntaxes, and eventually you want to get rid f the old stuff, but when... 💥 💥

So the best thing to do with standards based features like this, isn't to debate TypeScript's support or lack of support here, it is to find you friendly local TC39 rep and advocate that this feature is really important to you as well as participate in the proposal conversation linked to above on GitHub. The quicker the semantics get resolved and the quicker it gets to Stage 3, the quicker we can all have nice things!

Loading

@Toxicable
Copy link

@Toxicable Toxicable commented Nov 5, 2017

Now that rxjs has lettable operators this would be an even more awesome feature to have in Typescript
https://github.com/ReactiveX/rxjs/blob/master/doc/lettable-operators.md

Loading

@marty-wang
Copy link

@marty-wang marty-wang commented Nov 8, 2017

Can we have someone from TS team to shed some light on this request?

Loading

@kitsonk
Copy link
Contributor

@kitsonk kitsonk commented Nov 8, 2017

The have, they have tagged it ES Next and Suggestion... I can quote you chapter and verse other locations where they have commented on ES Next proposals and when and how they implement them...

A comment from them won't change anything. Do you think they are secretly working on it behind the scenes, waiting to spring it on the community? They often won't input on an issue if there is nothing to add... There is nothing to add to what has already been said.

Loading

@gajus
Copy link

@gajus gajus commented Jul 30, 2018

It has landed in Firefox under --enable-pipeline-operator compile flag.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Pipeline_operator

Loading

@lukeshiru
Copy link

@lukeshiru lukeshiru commented Jul 30, 2018

A minute of silence for the fallen hero, the bind operator ::, closed in favor of the evil |> 😢

Loading

@AlexGalays
Copy link

@AlexGalays AlexGalays commented Jul 30, 2018

Well I guess it's in the eye of the beholder, as I prefer |> :D

Loading

@zpdDG4gta8XKpMCd
Copy link

@zpdDG4gta8XKpMCd zpdDG4gta8XKpMCd commented Jul 31, 2018

hail to the king!

Loading

@AlexGalays
Copy link

@AlexGalays AlexGalays commented Aug 2, 2018

And here I thought it was a pipe dream

Loading

@babakness
Copy link

@babakness babakness commented Aug 9, 2018

Pipeline is essentially a simple use case of the Identity Monad. Also, pipe is usually compose in reverse whereas pipeline is more like a pipe that is invoked right away.

Anyway, looking forward to seeing this in Typescript.

Loading

@BenBeattieHood
Copy link

@BenBeattieHood BenBeattieHood commented Aug 9, 2018

Loading

@graingert
Copy link
Author

@graingert graingert commented Aug 9, 2018

Loading

@zpdDG4gta8XKpMCd
Copy link

@zpdDG4gta8XKpMCd zpdDG4gta8XKpMCd commented Aug 9, 2018

the idea of infix functions for typescript is almost as old as typescript: #2319

Loading

@aminpaks
Copy link
Contributor

@aminpaks aminpaks commented Aug 9, 2018

I know a lot of people want this really bad, but I believe TypeScript should not implement any extra operator as long as they are not in stage 3. Things can change and of course there are some exceptions.

Loading

@BenBeattieHood
Copy link

@BenBeattieHood BenBeattieHood commented Aug 9, 2018

I think it would be worth trying as a compiler transformer, just to allow the community to explore the idea, and to measure popularity. It's a well-defined feature in other functional languages, so might be quite safe to explore.

Loading

@mAAdhaTTah
Copy link

@mAAdhaTTah mAAdhaTTah commented Aug 10, 2018

@BenBeattieHood We're in the process of implementing this in babel, so you'll be able to test it there. If you do test it in a compiler transformer, definitely take a look at the current proposals, as there are a few forms of the pipeline operator we're considering.

Loading

@MeirionHughes
Copy link

@MeirionHughes MeirionHughes commented Aug 10, 2018

I think it would need a lot of thought about how its used; specifically with regard to typing things like:

function where<T>(predicate: (x: T) => boolean) {
  return function* (items: Iterable<T>): Iterable<T> {
    for (const item of items) {
      if (predicate(item)) {
        yield item;
      }
    }
  };
}

[1, 2, 3] |> where(x=>x> 1)

at the moment with where(x => x > 1)([1,2,3]) it cannot infer what x is. the above is one reason I was hoping the :: op would win out, because it (at first glance) seems a lot easier for typescript to infer what this is

Loading

@AlexGalays
Copy link

@AlexGalays AlexGalays commented Aug 10, 2018

Or we can see it another way: should it get released, It will prioritize some of the inference issues TS has 👍

Loading

@Bnaya
Copy link

@Bnaya Bnaya commented Aug 10, 2018

If you follow the spec and babel news, the spec is not set yet. There are 2 proposals. Im sure the typescript team will add support when the spec is finalized

Loading

@KiaraGrouwstra
Copy link
Contributor

@KiaraGrouwstra KiaraGrouwstra commented Aug 10, 2018

Infix functions ftw

iirc JS calls these "methods".

Loading

@masaeedu
Copy link
Contributor

@masaeedu masaeedu commented Sep 2, 2018

iirc JS calls these "methods"

@tycho01 Your comment is probably tongue in cheek, but I think these aren't quite the same thing. You can't just export a binary function from somewhere and apply it infix to two values; knowledge about every function that's ever going to manipulate the value must be grafted onto the value itself, either as a direct property or on the prototype. This is pretty inconvenient in practical scenarios.

Loading

@gajus
Copy link

@gajus gajus commented Dec 6, 2018

@BenBeattieHood We're in the process of implementing this in babel, so you'll be able to test it there. If you do test it in a compiler transformer, definitely take a look at the current proposals, as there are a few forms of the pipeline operator we're considering.

Babel parser now supports smart pipeline proposal.

babel/babel#8289

Loading

@linguofeng
Copy link

@linguofeng linguofeng commented Mar 14, 2019

Any updates?

Loading

@kitsonk
Copy link
Contributor

@kitsonk kitsonk commented Mar 14, 2019

Any updates?

🤦‍♂️ TypeScript does not implement proposal until they reach Stage 3. The pipeline operator is currently Stage 1 and has serious issues. That information has been provided multiple times in this thread.

Loading

@zpdDG4gta8XKpMCd
Copy link

@zpdDG4gta8XKpMCd zpdDG4gta8XKpMCd commented Mar 14, 2019

an example of serious issues please?

Loading

@MeirionHughes
Copy link

@MeirionHughes MeirionHughes commented Mar 14, 2019

maybe...

Warning: The details of the pipeline syntax are currently unsettled. There are two competing proposals under consideration.

Loading

@kitsonk
Copy link
Contributor

@kitsonk kitsonk commented Mar 14, 2019

Yes, that is what I consider a serious issue.

Loading

@RyanCavanaugh
Copy link
Member

@RyanCavanaugh RyanCavanaugh commented Mar 15, 2019

Going to lock this one since all threads in Waiting for TC39 status tend to just go in circles.

Ping!

Loading

@microsoft microsoft locked and limited conversation to collaborators Mar 15, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet