Typescript: Support proposed ES Next "|>" pipeline operator

Created on 10 Aug 2017  ·  79Comments  ·  Source: microsoft/TypeScript

ES Next Suggestion Waiting for TC39

Most helpful comment

It's in stage 1 now

All 79 comments

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

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

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.

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?

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!

@PublicParadise way too much boilerplate :p

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.

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

It's in stage 1 now

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

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.

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.

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

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

On 26 Oct 2017 19:01, "Tycho Grouwstra" notifications@github.com wrote:

@garkin https://github.com/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 https://github.com/Microsoft/TypeScript/issues/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.


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
https://github.com/Microsoft/TypeScript/issues/17718#issuecomment-339748284,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAZQTO6UiVHbrM6SRwaBhm8obaa3R7e9ks5swMkCgaJpZM4OzVEg
.

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

Twice the build time though :p

Would be nice if typescript was just a Babel plugin then you'd not need to
pipe through both programs

On 26 Oct 2017 20:16, "AlexGalays" notifications@github.com wrote:

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

Twice the build time though :p


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
https://github.com/Microsoft/TypeScript/issues/17718#issuecomment-339769856,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAZQTEArBw8jj0BcZFM2yLj5ErfbtNrgks5swNqagaJpZM4OzVEg
.

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

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?

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.

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

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

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

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 https://github.com/types/npm-ramda/issues/86), e.g. R.pipe(R.identity).

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!

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

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

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.

At this point I believe it would be so useful to have plugins that extend the language. Add features that they are not going to be part of the core of language would be parts of these plugins.

@aminpaks I don't really love the idea since it could quickly lead to babelification (as in the Tower of Babel not the excellent transpiler Babeljs 😀)

Since plugins would imply type level behavior, it becomes difficult to understand the meaning of programs and handling source level dependencies would require a number of complex but very useful features that TypeScript is currently lacking.

As much as I would _love_ this to be everywhere possible, I'm glad that TS is taking a more reserved approach to implementing new features and sticking with standards. This stance makes TS more appealing overall to folks like myself who care deeply about deviating from the standard but enjoy the advanced features transpilation provides ahead of browser vendor / JS engine adoption (but not necessarily ahead of standardization). It's a delicate balancing act.

Personally, I'm not in-favor of the pipeline operator at the expense of the bind-operator. It comes down to readability really.

Given:

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

bind-op:

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

pipeline-op:

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

ts-team are right in not bothering until stage-3; and even then stage-3 proposals can still be withdrawn (e.g. SIMD). It is a shame we can't have plugins though - the "risk" is passed to the individual developer. There are plenty of other use-cases for plugins too, not least to support things like .vue files containing typescript.

@MeirionHughes I agree. While I would rather have the pipeline operator, than no sugar at all, it is inspired by languages where functions are auto-curried and where libraries are built around leveraging that. It also assumes pipeline methods are _not_ members of the pipeline values.

So then you would have

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

function select<T, R>(projection: (item: T) => R): (items: Itererable<T>) => Itererable<R> {
  return function* () {
    for (const item of items) yield projection(item);
  };
}

and then you would write

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

but since JavaScript does not and can not auto-curry functions, it only seems to work well with libraries that are designed with currying in mind.

I might be mistaken about this, I'm not very current on the proposal.

Then a new library that provides curried util functions by default might become the new standard :p

@AlexGalays I think that is likely if this passes. As long as it isn't part of a subversive plot to turn JavaScript into OCaml, then all is well.

@MeirionHughes your example is not correct. Your where function doesn't use this at all, so the bind operator wouldn't work. The bind operator also has a lot of open questions for type safety. Would where be allowed to access private properties from this like a class method? If not, what is the point of using this? If yes, then changing private properties is suddenly a semver-breaking change, which completely defeats the purpose of private properties.
You also state that the syntax is less readable, but for example omit the parens in your bind-operator example but add unneeded parens in the pipeline example. And the pipeline operator will of course not work at all with functions written for binding, but it will work well for curried functions, like rxjs operators, ramda or lodash/fp.

@aluanhaddad Besides all the fp libraries, RxJS is an example of a widely used library that moved from operators on prototypes (which have lots of problems, mostly around tree-shaking and generic type safety) to operators as curried functions. Most libraries probably do not choose that way right now _because_ we do not have a good syntax for it.

@felixfbecker you're right and @aluanhaddad 's example that nests the resulting functions that get piped has changed my mind completely.

Has anyone thought about or is implementing a custom transformer to get pipeline support a bit earlier? From the looks of it it could be done via a custom transformer that simply has babel transpile _just_ the pipeline part itself. you could then execute it via: https://github.com/cevek/ttypescript

Is that a possibility? Using custom transforms to use Babel-supported supported syntax, while keeping things like TypeScripts tooling working?

Maybe? there is a preset that only deals with the 0-2 proposals: https://www.npmjs.com/package/babel-preset-proposal-typescript - it implies you put this in before sending it to typescript. You're intellisense will likely be borked though. However, with https://github.com/cevek/ttypescript#visual-studio-code you might get away with it.

@MeirionHughes I'm glad that was helpful ❤️.
Now we just need #6606 so that we can transform methods of arbitrary prototypes into curried functions!

TypeScript is a babel transform now, I think there should be a way to sequence in the pipeline desugaring before the TypeScript pass. I have no idea how you'd make that work with the language server though.

I added a self-hosted implementation for TS #22816

As one of the people pushing for the pipeline operator, I beg you: Please do _not_ implement this in TypeScript until it's advanced further. We're still debating two potential proposals, which are fundamentally incompatible with each other, so TypeScript is begging for a world a pain if it implements this too early.

If you're interested in this proposal, take a look at the repo here and get involved: https://github.com/tc39/proposal-pipeline-operator/ We'd love your feedback! And we're working on Babel plugins for the differing proposals, so you'll have an opportunity to try them out in your (non-TypeScript) projects.

But the proposal is not ready to land in something like TypeScript yet.

We're definitely not merging this.

Could we have something like https://github.com/babel/babel-eslint, that allows us to continue using features supported by Babel, and have the typechecking work after features unsupported by TypeScript have been desugared away?

@masaeedu Yes! This

@MeirionHughes with the partial application proposal it becomes easier:

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

@bernatmv: fwiw there's something close to it that works today.

@tycho01 But not in TypeScript, not until it gets 2.8 typings: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/25067

@jeremejevs @bernatmv actually, R.__ has been typed using codegen in npm-ramda. Better ways using 2.8 welcome!

I'm somewhat new to Javascript and TypeScript (2 weeks) so please forgive me if there is a simpler solution out there. But below is what I've come up with in the absence of a pipeline operator. I originally tried having multiple overloads of pipe that worked with 2, 3, 4, etc. type parameters, but couldn't figure out how to get TypeScript overload resolution to work like it does in C#. We could have different functions pipe1<A,B>, pipe2<A,B,C>, and pipe3<A,B,C,D> but this would be tough to work with since you'd have to choose the function name based on how many arguments you wanted. Is there a simpler type-safe solution than what I've proposed below? Is there a recursive type definition that could accept an unlimited umber of parameters? Am I using condition types properly?

type LastOf<
    A,
    B=never,
    C=never,
    D=never,
    E=never,
    F=never,
    G=never,
    H=never,
    I=never,
    J=never> =
    [B] extends [never] ? A :
    [C] extends [never] ? B :
    [D] extends [never] ? C :
    [E] extends [never] ? D :
    [F] extends [never] ? E :
    [G] extends [never] ? F :
    [H] extends [never] ? G :
    [I] extends [never] ? H :
    [J] extends [never] ? I :
    J;

export function pipe<A, B, C=never, D=never, E=never, F=never, G=never, H=never, I=never, J=never>(
    a: A,
    mapA: (a: A) => B,
    mapB?: (b: B) => C,
    mapC?: (c: C) => D,
    mapD?: (d: D) => E,
    mapE?: (e: E) => F,
    mapF?: (f: F) => G,
    mapG?: (g: G) => H,
    mapH?: (h: H) => I,
    mapI?: (i: I) => J
): LastOf<A, B, C, D, E, F, G, H, I, J> {
    if (mapB === undefined) {
        return mapA(a) as LastOf<A, B, C, D, E, F, G, H, I, J>;
    }
    if (mapC === undefined) {
        return mapB(mapA(a)) as LastOf<A, B, C, D, E, F, G, H, I, J>;
    }
    if (mapD === undefined) {
        return mapC(mapB(mapA(a))) as LastOf<A, B, C, D, E, F, G, H, I, J>;
    }
    if (mapE === undefined) {
        return mapD(mapC(mapB(mapA(a)))) as LastOf<A, B, C, D, E, F, G, H, I, J>;
    }
    if (mapF === undefined) {
        return mapE(mapD(mapC(mapB(mapA(a))))) as LastOf<A, B, C, D, E, F, G, H, I, J>;
    }
    if (mapG === undefined) {
        return mapF(mapE(mapD(mapC(mapB(mapA(a)))))) as LastOf<A, B, C, D, E, F, G, H, I, J>;
    }
    if (mapH === undefined) {
        return mapG(mapF(mapE(mapD(mapC(mapB(mapA(a))))))) as LastOf<A, B, C, D, E, F, G, H, I, J>;
    }
    if (mapI === undefined) {
        return mapH(mapG(mapF(mapE(mapD(mapC(mapB(mapA(a)))))))) as LastOf<A, B, C, D, E, F, G, H, I, J>;
    }
    return mapI(mapH(mapG(mapF(mapE(mapD(mapC(mapB(mapA(a))))))))) as LastOf<A, B, C, D, E, F, G, H, I, J>;
}

test("map once", () => {
    const result = pipe(
        2,
        i => i * 10);
    expect(result).toBe(20);
});

test("map twice", () => {
    const result = pipe(
        2,
        i => i * 10,
        i => `the answer is ${i}`);
    expect(result).toBe('the answer is 20');
});

test("map three times", () => {
    const result = pipe(
        2,
        i => i * 10,
        i => -i,
        i => ({ a: i, b: -i }));
    expect(result).toEqual({ a: -20, b: 20 });
});

I thought lodash/fp's _.flow had this typed out already?

On Wed, 9 May 2018, 22:19 jmagaram, notifications@github.com wrote:

I'm somewhat new to Javascript and TypeScript (2 weeks) so please forgive
me if there is a simpler solution out there. But below is what I've come up
with in the absence of a pipeline operator. I originally tried having
multiple overloads of pipe that worked with 2, 3, 4, etc. type
parameters, but couldn't figure out how to get TypeScript overload
resolution to work like it does in C#. We could have different functions
pipe1, pipe2, and pipe3 but this would be tough to
work with since you'd have to choose the function name based on how many
arguments you wanted. Is there a simpler type-safe solution than what I've
proposed below? Is there a recursive type definition that could accept an
unlimited umber of parameters? Am I using condition types properly?

type LastOf =
[B] extends [never] ? A :
[C] extends [never] ? B :
[D] extends [never] ? C :
[E] extends [never] ? D :
[F] extends [never] ? E :
F;

function pipe(
a: A,
mapA: (a: A) => B,
mapB?: (b: B) => C,
mapC?: (c: C) => D,
mapD?: (d: D) => E,
mapE?: (e: E) => F): LastOf {
const b = mapA(a);
switch (mapB) {
case undefined: return b as LastOf;
default: {
const c = mapB(b);
switch (mapC) {
case undefined: return c as LastOf;
default: {
const d = mapC(c);
switch (mapD) {
case undefined: return d as LastOf;
default: {
const e = mapD(d);
switch (mapE) {
case undefined: return e as LastOf;
default: return mapE(e) as LastOf;
}
}
}
}
}
}
}
}

test("map once", () => {
const result = pipe(
2,
i => i * 10);
expect(result).toBe(20);
});

test("map twice", () => {
const result = pipe(
2,
i => i * 10,
i => the answer is ${i});
expect(result).toBe('the answer is 20');
});

test("map three times", () => {
const result = pipe(
2,
i => i * 10,
i => -i,
i => ({ a: i, b: -i }));
expect(result).toEqual({ a: -20, b: 20 });
});


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/Microsoft/TypeScript/issues/17718#issuecomment-387878691,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAZQTLm6LrWe5KVx4aGBFUd4yRUHkkrZks5tw11cgaJpZM4OzVEg
.

I just looked at lodash and you are correct - the flow function accepts many parameters and is strongly typed. Iit doesn't look like it is done in pure TypeScript. They use a type definition file for all the overloads. I'm not sure if this is better or worse than trying to do it all in TypeScript.

@jmagaram all TS generally makes it easier due to inference, but if it works it works.

@jmagaram A somewhat simpler pure typescript alternative could look something like this.

interface IPipe<T> {
    readonly value: () => T;
    chain<R>(fn: (x: T) => R): IPipe<R>;
}

function pipe<T>(val: T): IPipe<T> {
    return {
        chain: fn => pipe(fn(val)),
        value: () => val
    };
}

Usage would still be fairly clean and strongly typed.

pipe(["Hello", "There"])
    .chain(map(x => `${x}!`))
    .chain(xs => {
        ...
    })
    .value()

I'd really appreciate the ability to add custom operators. F# has a nice approach for this.

In the meantime, here's a simpler approach without the wrapping:

function pipe<T1>(first:T1):T1
function pipe<T1, T2>(first:T1, second:(a:T1) => T2):T2
function pipe<T1, T2, T3>(first:T1, second:(a:T1) => T2, third:(a:T2) => T3):T3
function pipe<T1, T2, T3, T4>(first:()=>T1, second:(a:T1)=>T2, third:(a:T2)=>T3, fourth:(a:T3)=>T4):T4
function pipe<T1, T2, T3, T4, T5>(first:()=>T1, second:(a:T1)=>T2, third:(a:T2)=>T3, fourth:(a:T3)=>T4, fifth:(a:T4)=>T5):T5
function pipe<T1, T2, T3, T4, T5, T6>(first:()=>T1, second:(a:T1)=>T2, third:(a:T2)=>T3, fourth:(a:T3)=>T4, fifth:(a:T4)=>T5, sixth:(a:T5)=>T6):T6
function pipe<T1, T2, T3, T4, T5, T6, T7>(first:()=>T1, second:(a:T1)=>T2, third:(a:T2)=>T3, fourth:(a:T3)=>T4, fifth:(a:T4)=>T5, sixth:(a:T5)=>T6, seventh:(a:T6)=>T7):T7
function pipe<T1, T2, T3, T4, T5, T6, T7, T8>(first:()=>T1, second:(a:T1)=>T2, third:(a:T2)=>T3, fourth:(a:T3)=>T4, fifth:(a:T4)=>T5, sixth:(a:T5)=>T6, seventh:(a:T6)=>T7, eigth:(a:T7)=>T8):T8
function pipe<T1, T2, T3, T4, T5, T6, T7, T8, T9>(first:()=>T1, second:(a:T1)=>T2, third:(a:T2)=>T3, fourth:(a:T3)=>T4, fifth:(a:T4)=>T5, sixth:(a:T5)=>T6, seventh:(a:T6)=>T7, eigth:(a:T7)=>T8, ninth:(a:T8)=>T9):T9
function pipe<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(first:()=>T1, second:(a:T1)=>T2, third:(a:T2)=>T3, fourth:(a:T3)=>T4, fifth:(a:T4)=>T5, sixth:(a:T5)=>T6, seventh:(a:T6)=>T7, eigth:(a:T7)=>T8, ninth:(a:T8)=>T9, tenth:(a:T9)=>T10):T10
function pipe(first:any, ...args:Function[]):any {
    return (
        args && args.length 
        ? args.reduce(
            (result, next) => next(result),
            first instanceof Function ? first() : first
        )
        : first instanceof Function ? first() : first
    );
}

This gives:
ts-pipe-example
(for more info, see here)

That said, @graingert +1 you're right: lodash has this already for composition (but not pipe):

const getUpperName = 
   _.flow(
      (p: Person) => `${p.FirstName} ${p.LastName}`,
      (s: string) => s.toUpper()
   )

Alternatively, you could instead add pipe to the Object.prototype:

Object.prototype.pipe = function<Self, Result>(this:Self, next:(value:Self) => Result):Result {
    return next(this)
}

This allows instead:
capture
(for more info, see here)

Hope this helps others!

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

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

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

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

hail to the king!

And here I thought it was a pipe dream

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.

Although having pipeline would be helpful, I feel it would be possible to offer the ability to define custom operators (functions whose name may include special chars, and whose first param is on their left) via a compiler transformer. Anyone interested in trying this with me, or have some background on this?

On Fri, Aug 10, 2018, 02:53 Babak notifications@github.com wrote:

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.


You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/Microsoft/TypeScript/issues/17718#issuecomment-411824741,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AFXUx5rwM9wVrpAkHK2BNYkyy74HtWU5ks5uPGkNgaJpZM4OzVEg
.

--

Infix functions ftw

On Thu, 9 Aug 2018, 23:35 Ben Beattie-Hood, notifications@github.com
wrote:

Although having pipeline would be helpful, I feel it would be possible to
offer the ability to define custom operators (functions whose name may
include special chars, and whose first param is on their left) via a
compiler transformer. Anyone interested in trying this with me, or have
some background on this?

On Fri, Aug 10, 2018, 02:53 Babak notifications@github.com wrote:

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.


You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<
https://github.com/Microsoft/TypeScript/issues/17718#issuecomment-411824741
,
or mute the thread
<
https://github.com/notifications/unsubscribe-auth/AFXUx5rwM9wVrpAkHK2BNYkyy74HtWU5ks5uPGkNgaJpZM4OzVEg

.

--


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/Microsoft/TypeScript/issues/17718#issuecomment-411919587,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAZQTHHFbVY5uGCWl-La_P-HF7UN6xPsks5uPLk8gaJpZM4OzVEg
.

the idea of infix functions for typescript is almost as old as typescript: https://github.com/Microsoft/TypeScript/issues/2319

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.

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.

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

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

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

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

Infix functions ftw

iirc JS calls these "methods".

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.

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

https://github.com/babel/babel/pull/8289

Any updates?

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.

an example of serious issues please?

maybe...

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

Yes, that is what I consider a serious issue.

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

Ping!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jbondc picture jbondc  ·  3Comments

CyrusNajmabadi picture CyrusNajmabadi  ·  3Comments

manekinekko picture manekinekko  ·  3Comments

wmaurer picture wmaurer  ·  3Comments

DanielRosenwasser picture DanielRosenwasser  ·  3Comments