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

Closed
graingert opened this issue Aug 10, 2017 · 80 comments
Closed

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

graingert opened this issue Aug 10, 2017 · 80 comments
Labels
ES Next New featurers for ECMAScript (a.k.a. ESNext) Suggestion An idea for TypeScript Waiting for TC39 Unactionable until TC39 reaches some conclusion

Comments

@graingert
Copy link

No description provided.

@AlexGalays
Copy link

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

@DanielRosenwasser DanielRosenwasser added ES Next New featurers for ECMAScript (a.k.a. ESNext) Suggestion An idea for TypeScript labels Aug 10, 2017
@aluanhaddad
Copy link
Contributor

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

@Pajn
Copy link

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.

@AlexGalays
Copy link

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?

@PublicParadise
Copy link

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!

@AlexGalays
Copy link

@PublicParadise way too much boilerplate :p

@aluanhaddad
Copy link
Contributor

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.

@KiaraGrouwstra
Copy link
Contributor

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

@ddimaria
Copy link

It's in stage 1 now

@tkgalk
Copy link

tkgalk commented Sep 29, 2017

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

@kitsonk
Copy link
Contributor

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.

@garkin
Copy link

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.

@KiaraGrouwstra
Copy link
Contributor

@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.

@graingert
Copy link
Author

graingert commented Oct 26, 2017 via email

@AlexGalays
Copy link

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

Twice the build time though :p

@graingert
Copy link
Author

graingert commented Oct 26, 2017 via email

@garkin
Copy link

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.

@kbtz
Copy link

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?

@garkin
Copy link

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.

@felixfbecker
Copy link
Contributor

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.

@AlexGalays
Copy link

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.

@felixfbecker
Copy link
Contributor

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

https://github.com/DefinitelyTyped/DefinitelyTyped/blob/b67c928904f03d0911c99ab938b14bc2e59cad40/types/lodash/index.d.ts#L7819-L7855

@KiaraGrouwstra
Copy link
Contributor

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).

@kitsonk
Copy link
Contributor

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!

@Toxicable
Copy link

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

@marty-wang
Copy link

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

@kitsonk
Copy link
Contributor

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.

@loucyx
Copy link

loucyx commented Jul 30, 2018

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

@AlexGalays
Copy link

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

@zpdDG4gta8XKpMCd
Copy link

hail to the king!

@AlexGalays
Copy link

And here I thought it was a pipe dream

@babakness
Copy link

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.

@BenBeattieHood
Copy link

BenBeattieHood commented Aug 9, 2018 via email

@graingert
Copy link
Author

graingert commented Aug 9, 2018 via email

@zpdDG4gta8XKpMCd
Copy link

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

@aminpaks
Copy link
Contributor

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.

@BenBeattieHood
Copy link

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.

@mAAdhaTTah
Copy link

@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.

@MeirionHughes
Copy link

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

@AlexGalays
Copy link

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

@Bnaya
Copy link

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

@KiaraGrouwstra
Copy link
Contributor

Infix functions ftw

iirc JS calls these "methods".

@RyanCavanaugh RyanCavanaugh added the Waiting for TC39 Unactionable until TC39 reaches some conclusion label Aug 15, 2018
@masaeedu
Copy link
Contributor

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.

@gajus
Copy link

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

@linguofeng
Copy link

Any updates?

@kitsonk
Copy link
Contributor

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.

@zpdDG4gta8XKpMCd
Copy link

an example of serious issues please?

@MeirionHughes
Copy link

maybe...

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

@kitsonk
Copy link
Contributor

kitsonk commented Mar 14, 2019

Yes, that is what I consider a serious issue.

@RyanCavanaugh
Copy link
Member

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

Ping!

@microsoft microsoft locked and limited conversation to collaborators Mar 15, 2019
@RyanCavanaugh
Copy link
Member

Marking this as closed since there's nothing for us to do until something happens at TC39. We're working with some other members to continue to iterate on the active proposals, so please contribute feedback there until that proposal (or its kin) reaches stage 3.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
ES Next New featurers for ECMAScript (a.k.a. ESNext) Suggestion An idea for TypeScript Waiting for TC39 Unactionable until TC39 reaches some conclusion
Projects
None yet
Development

No branches or pull requests