简体   繁体   中英

How functionally compose functions ( f(g(h))) in RxJS?

So... in Underscore there is a function called "compose" http://underscorejs.org/#compose Returns the composition of a list of functions, where each function consumes the return value of the function that follows. In math terms, composing the functions f(), g(), and h() produces f(g(h())). Returns the composition of a list of functions, where each function consumes the return value of the function that follows. In math terms, composing the functions f(), g(), and h() produces f(g(h())).

And even without Underscore you can achieve the same effect by using reduce:

var mappers = [
    function add_100(v) {
        return v + 100;
    },
    function mul_2(v) {
        return v * 2;
    }
];

console.log(
    [1, 2, 3].map(function (n) {
        // we transform n by passing it to the all mappers
        // so result will be: mul_2( add_100(n) )
        return mappers.reduce(function (currentResult, mapper) {
            return mapper(currentResult);
        }, n /* initial value for pipeline */ )
    })
); // ---> [202, 204, 206] 

// because: 
// (1 + 100) * 2 = 202
// (2 + 100) * 2 = 204
// (3 + 100) * 2 = 206

But this approach is good when functions add_100 or mul_2 are synchronous and returns value immediately. Because I need asynchronous functions I started using RxJS.

And off course, there is a reduce operator in RxJS but it seems to work synchronously, like in VanillaJS.

var Rx = require('rx')
var mappers = Rx.Observable.from([
    function add_100(v) {
        return v + 100; // works in Rx because it's synchronous 
    },
    function mul_2(v) {
        return v * 2; // works in Rx because it's synchronous
    }
]);

Rx.Observable.from([1, 2, 3]).map(function (n) {
    // we transform n by passing it to the all mappers
    // so result will be: mul_2( add_100(n) )
    return mappers.reduce(function (currentResult, mapper) {
        return mapper(currentResult);
    }, n /* initial value for pipeline */ )
}).mergeAll().subscribe(console.log)
// 202
// 204
// 206

When functions add_100, and mul_2 are asynchronous eg returns Observable instead of immediate value it doesn't work anymore.

So... I actually make some solution to walk around this but it looks ugly and not very clear:

var Rx = require('rx')
var mappers = Rx.Observable.from([
    function add_100(v) {
        return Rx.Observable.create(
            o => {
                // line below concerns me most:
                v.subscribe( value => o.onNext(value + 100) );
            }
        );

        return Rx.Observable.from(v + 100);
    },
    function mul_2(v) {
        return Rx.Observable.create(
            o => {
                v.subscribe( value => o.onNext(value * 2) )
            }
        );
    }
]);

Rx.Observable.from([1, 2, 3]).map(function (n) {
    return mappers.reduce(function (currentResult, mapper) {
        return mapper(currentResult);
    }, Rx.Observable.from([n]) /* initial value for pipeline */ ).mergeAll();
}).mergeAll().subscribe(console.log)

note that I'm using arrow notations from ES6. In ES5 it would look even uglier.

What I really wanted is something like this:

Rx.Observable.from([1, 2, 3])
.map(Rx.compose(mappers))  // my imaginary Rx.compose util like in Underscore
.mergeAll()
.subscribe(console.log)

Or at least some nicer that hacky solution I've figure out. Off course I could make my own utility for that, but this is not a problem. I wonder if there is maybe some nice Rx-way solution for these kinds of problems? Rx is high level library so I thought maybe I won't rediscover wheel but ask what people more experienced in Rx/reactive approach would say.

Or maybe inb4 I'll hear "your solution is elegant, nothing wrong with that". Maybe. I just feel mental overhead when I look at this code.

I've figured out very simple solution:

var Rx = require('rx');
var mappers = [ 
    function add_100(v) {
        return Rx.Observable.return(v + 100);
    },
    function mul_2(v) {
        return Rx.Observable.return(v * 2);
    }
];

mappers.reduce( // magic happens here
    (o, mapper) => o.flatMap(mapper), // and exactly here.  
    Rx.Observable.from([1, 2, 3]) // initial observable we want to transform
).subscribe(console.log);

// 202
// 204
// 206

It's functionally equivalent to this:

// ... (initializing code the same like above)

Rx.Observable.from([1, 2, 3])
  .flatMap(mappers[0])
  .flatMap(mappers[1])
  .subscribe(console.log);

Only made in more flexible way (number of mappers is unknown).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM