typescriptfunctional-programmingfunction-compositiontransducer

How to functional compose transforms of objects via transducers


Live code example

I'm trying to learn transducers via egghead and I think I got it until we try to compose object transformation. I have an example below that doesn't work

const flip = map(([k,v]) => ({[v]: k}));
const double = map(([k,v]) => ({[k]: v + v}));
seq(flip, {one: 1, two: 2}); /*?*/ {1: 'one', 2: 'two'}
seq(double, {one: 1, two: 2}); /*?*/ {'one': 2, 'two: 4}

but if I compose it fails:

seq(compose(flip, double), {one: 1, two: 2}); /*?*/ {undefined: NaN}
seq(compose(double, flip), {one: 1, two: 2}); /*?*/ {undefined: undefined} 

How can I work with objects using transducers with fp composition?

There is quite a bit of boiler plate so I really suggest looking at the live code example to review the utils like compose, seq etc.


Solution

  • first off thanks for going through the course. You're having trouble composing because we've got clashing data types between the expected inputs and outputs.

    When composing flip and double, the seq helper calls the transduce helper function which will convert your input object to an array of [k,v] entries so that it can iterate through it. It also calls your composed transform with the objectReducer helper to be used as the inner reducer, which just does an Object.assign to keep building up the accumulation.

    It then iterates through the [k,v] entries, passing them to your composed reducer, but it's up to you to ensure you keep the data types compatible between your transforms.

    In your example, double will get the return value of flip, but double expects a [k,v] array, and flip returns an object.

    So you would have to do something like this:

    const entriesToObject = map(([k,v]) => {
      return {[k]:v};
    });
    const flipAndDouble = compose(
      map(([k,v]) => {
        return [k,v+v];
      }),
      map(([k,v]) => {
        return [v,k];
      }),
      entriesToObject,
    );
    
    //{ '2': 'one', '4': 'two', '6': 'three' }​​​​​
    

    It's a bit confusing since you have to ensure the last step returns an object and not a [k,v] array. This is so the objReducer that performs the Object.assign will work correctly since it expects an object as the value. This is why I added in entriesToObject above.

    If the objReducer was updated to handle [k,v] arrays as well as objects as values then you could keep returning [k,v] arrays from your last step as well, which is a much better approach

    You can see an example of how the objReducer could be rewritten here: https://github.com/jlongster/transducers.js/blob/master/transducers.js#L766

    For production use, if you use that transducer library, you can just keep treating your inputs and outputs as [k,v] arrays, which is a much better approach. For your own learning, you could try modifying the objReducer based on that link and you should then be able to remove entriesToObject from the composition above.

    Hope that helps!