paldepind / flyd

The minimalistic but powerful, modular, functional reactive programming library in JavaScript.
MIT License
1.56k stars 85 forks source link

Support multiple operators for Pipe #227

Open NoelAbrahams opened 7 months ago

NoelAbrahams commented 7 months ago

Hi — brilliant library

Simple question: I would like to do the following:

mystream
    .pipe(
       flyd.filter(value => value !== 'foo'),
       flyd.skip(1),
       flyd.dropRepeats()
    )
    .map((value) => {
        console.log(value);
    });

Instead of

mystream
    .pipe(flyd.filter(value => value !== 'foo'))
    .pipe(flyd.skip(1))
    .pipe(flyd.dropRepeats())
    .map((value) => {
        console.log(value);
    });

Basically to avoid repeating .pipe as it is much cleaner.

I've seen some discussion here: https://github.com/paldepind/flyd/issues/137

I'm assuming this pattern for pipe is somehow contrary to the design of flyd?

Thanks

nordfjord commented 7 months ago

I'm assuming this pattern for pipe is somehow contrary to the design of flyd?

IIRC the reason we went for a single function passed to pipe was because we didn't want to introduce the complicated TS required to type such a pipe function.

Ultimately pipe is a pretty generic thing, have you considered using something like remeda's pipe?

R.pipe(
    mystream,
    flyd.filter(value => value !== 'foo'),
    flyd.skip(1),
    flyd.dropRepeats(),
    flyd.map(console.log)
)
NoelAbrahams commented 7 months ago

It will be nice to have this supported by the flyd lib. I've had a look at the API of a bunch of other similar libraries but I really like the fact that you have gone with pipe (which many other's don't seem to have).

There's nothing terribly wrong with chaining multiple pipe calls, but in the land of FRP these small deviations can grate, something explained quite well in this article on FRP.

Happy to help with the TS bits if needed.

nordfjord commented 7 months ago

I would hesitate to change it if it's only for stylistic preference. As mentioned, pipe is a pretty generic function that you can easily implement outside any library and can then use with any of your types.

const pipe = (x, f, ...fs) => fs.reduce((x, f) => f(x), f(x))
NoelAbrahams commented 5 months ago

I've added the following to the lib locally:

Code

    function operator_pipe(...fns) {
        return fns.reduce((x, f) => f(x), this); 
    }

Typing

pipe<R>(op: OperatorFunction<T, R>): Stream<R>;
pipe<A1, V>(op0: OperatorFunction<T, A1>, op1: OperatorFunction<A1, V>): Stream<V>;
pipe<A1, A2, V>(op0: OperatorFunction<T, A1>, op1: OperatorFunction<A1, A2>, op2: OperatorFunction<A2, V>): Stream<V>;
pipe<A1, A2, A3, V>(
    op0: OperatorFunction<T, A1>,
    op1: OperatorFunction<A1, A2>,
    op2: OperatorFunction<A2, A3>,
    op3: OperatorFunction<A3, V>,
): Stream<V>;
pipe<A1, A2, A3, A4, V>(
    op0: OperatorFunction<T, A1>,
    op1: OperatorFunction<A1, A2>,
    op2: OperatorFunction<A2, A3>,
    op3: OperatorFunction<A3, A4>,
    op4: OperatorFunction<A4, V>,
): Stream<V>;

Where

    interface UnaryFunction<T, R> {
        (source: T): R;
    }
    interface OperatorFunction<T, R> extends UnaryFunction<Stream<T>, Stream<R>> {}

This works for me. Happy to close the issue if it's not going to be supported.