4

How can I compose two functions together into 1 using FP's compose() here's live code: https://repl.it/JXMl/1

I have 3 pure functions:

// groups By some unique key
const groupBy = function(xs, key) {
  return xs.reduce(function(rv, x) {
    (rv[x[key]] = rv[x[key]] || []).push(x);
    return rv;
  }, {});
};

// ungroups the group by
const ungroup = (obj) => {
  return Object.keys(obj)
               .map(x => obj[x]);
};

// flatten array
const flatten = (arrs) => {
  return arrs.reduce((acc, item) => acc.concat(item), [])
}

And a functional utility compose function from Functional Jargon

const compose = (f, g) => (a) => f(g(a))

In the end, I want a ungroupAndFlatten function created through compose().

Along the lines of:

const ungroupAndFlatten = compose(ungroup, flatten) // Usage doesn't work.
console.log(ungroupAndFlatten(obj)) 

Example code:

const arrs = [
  {name: 'abc', effectiveDate: "2016-01-01T00:00:00+00:00"},
  {name: 'abcd', effectiveDate: "2016-02-01T00:00:00+00:00"},
  {name: 'abcd', effectiveDate: "2016-09-01T00:00:00+00:00"},
  {name: 'abc', effectiveDate: "2016-04-01T00:00:00+00:00"},
  {name: 'abc', effectiveDate: "2016-05-01T00:00:00+00:00"},
]; 

const groupedByName = groupBy(arrs, 'name');

// Example Output
//
// var obj = {
//    abc: [
//      { name: 'abc', effectiveDate: '2016-01-01T00:00:00+00:00' },
//      { name: 'abc', effectiveDate: '2016-04-01T00:00:00+00:00' },
//      { name: 'abc', effectiveDate: '2016-05-01T00:00:00+00:00' }
//    ],
//    abcd: [ 
//      { name: 'abcd', effectiveDate: '2016-02-01T00:00:00+00:00' },
//      { name: 'abcd', effectiveDate: '2016-09-01T00:00:00+00:00' }
//    ]
//  }

const ungroupAndFlatten = compose(ungroup, flatten) // Usage doesn't work.
console.log(ungroupAndFlatten(groupedByName)) 

// Output:
//  var arrs = [
//    {name: 'abc', effectiveDate: "2016-01-01T00:00:00+00:00"},
//    {name: 'abcd', effectiveDate: "2016-02-01T00:00:00+00:00"},
//    {name: 'abcd', effectiveDate: "2016-09-01T00:00:00+00:00"},
//    {name: 'abc', effectiveDate: "2016-04-01T00:00:00+00:00"},
//    {name: 'abc', effectiveDate: "2016-05-01T00:00:00+00:00"},
//  ];

4 Answers 4

4

I believe you made a simple mistake,you've set ungroup as your f function while it is your g function:

const ungroupAndFlatten = compose(flatten, ungroup)

switch ungroup and flatten and everything will work fine

Sign up to request clarification or add additional context in comments.

4 Comments

Damn you're right! lol Weird that it goes from right to left and not left to right.
@MatthewHarwood It goes from right to left because that's how it works in math.
@ftor could you bring a bit more clarity to the "thats how math goes?" lol Which math ? I see that the compose goes inside out in execution but does the API of compose is trying to mimic that ? right being the inside?
@MatthewHarwood The right-to-left order has prevailed because the notation gives a nice symmetry in math: (f∘g)(z) = f(g(z)). With the inverted order you'd get (g∗f)(z) = f(g(z)). That's all.
3

Function Composition

The function compose has a evaluation order from right to left.

/*
 * 1. take x
 * 2. execute g(x)
 * 3. take the return value of g(x) and execute f with it
*/
const compose = (f, g) => x => f(g(x))

There is another function called pipe which evaluates from left to right

const pipe = (g, f) => x => f(g(x))

Your Code

As already mentioned by @Nima Hakimi, you have reversed the argument order.

To solve you problem we could

  • switch the order of the arguments

    const ungroupAndFlatten = compose(
        flatten,
        ungroup
    )
    

    const compose = (f, g) => x => 
        f(g(x))
    
    const ungroup = obj =>
        Object.keys(obj)
            .map(x => obj[x]);
    
    const flatten = arrs =>
        arrs.reduce((acc, item) => acc.concat(item), [])
    
    const groupedByName = {
        abc: [
            { name: 'abc', effectiveDate: '2016-01-01T00:00:00+00:00' },
            { name: 'abc', effectiveDate: '2016-04-01T00:00:00+00:00' },
            { name: 'abc', effectiveDate: '2016-05-01T00:00:00+00:00' }
        ],
        abcd: [
            { name: 'abcd', effectiveDate: '2016-02-01T00:00:00+00:00' },
            { name: 'abcd', effectiveDate: '2016-09-01T00:00:00+00:00' }
        ]
    }
    
    const ungroupAndFlatten = compose(
        flatten,
        ungroup
    )
    
    console.log(
      ungroupAndFlatten(groupedByName)
    )

  • or use pipe

    const ungroupAndFlatten = pipe(
        ungroup,
        flatten
    )
    

    const pipe = (g, f) => x => 
        f(g(x))
    
    const ungroup = obj =>
        Object.keys(obj)
            .map(x => obj[x]);
    
    const flatten = arrs =>
        arrs.reduce((acc, item) => acc.concat(item), [])
    
    const groupedByName = {
        abc: [
            { name: 'abc', effectiveDate: '2016-01-01T00:00:00+00:00' },
            { name: 'abc', effectiveDate: '2016-04-01T00:00:00+00:00' },
            { name: 'abc', effectiveDate: '2016-05-01T00:00:00+00:00' }
        ],
        abcd: [
            { name: 'abcd', effectiveDate: '2016-02-01T00:00:00+00:00' },
            { name: 'abcd', effectiveDate: '2016-09-01T00:00:00+00:00' }
        ]
    }
    
    const ungroupAndFlatten = pipe(
        ungroup,
        flatten
    )
    
    console.log(
      ungroupAndFlatten(groupedByName)
    )

Function Composition with n Arguments

And again we have

  • compose
    const compose = (...fns) => fns.reduceRight((f, g) => (...args) => 
        g(f(...args)))
    
  • pipe
    const pipe = (...fns) => fns.reduce((f, g) => (...args) => 
        g(f(...args)))
    

Our goal is it to compose to ungroup and flatten the function groupBy, too.
We could try

const groupByNameAndFlattenAndUngroup = compose(
    flatten,
    ungroup,
    groupBy('name', x)  // <-- this is our problem
)

but this will not work. We can only compose functions with one argument.. The solution is to rewrite groupBy to a curried version:

const groupBy = xs => key =>
xs.reduce((rv, x) => {
    (rv[x[key]] = rv[x[key]] || []).push(x)
    return rv
}, {})

const groupByNameAndFlattenAndUngroup = (key, xs) => compose(
    flatten,
    ungroup,
    groupBy ('name')
) (xs)

But this solution can be even shorter. If we switch the order of the arguments from groupBy to groupBy = key => xs => {/*..*/} we can do:

const groupBy = key => xs =>
    xs.reduce((rv, x) => {
        (rv[x[key]] = rv[x[key]] || []).push(x)
        return rv
    }, {})

const groupByNameAndFlattenAndUngroup = compose(
    flatten,
    ungroup,
    groupBy ('name')
)

Working Example

const arrs = [
  {name: 'abc', effectiveDate: "2016-01-01T00:00:00+00:00"},
  {name: 'abcd', effectiveDate: "2016-02-01T00:00:00+00:00"},
  {name: 'abcd', effectiveDate: "2016-09-01T00:00:00+00:00"},
  {name: 'abc', effectiveDate: "2016-04-01T00:00:00+00:00"},
  {name: 'abc', effectiveDate: "2016-05-01T00:00:00+00:00"},
]

const compose = (...fns) => fns.reduceRight((f, g) => (...args) => 
    g(f(...args)))
    
const ungroup = obj =>
    Object.keys(obj)
        .map(x => obj[x]);

const flatten = arrs =>
    arrs.reduce((acc, item) => acc.concat(item), [])
    
const groupBy = key => xs =>
  xs.reduce((rv, x) => {
      (rv[x[key]] = rv[x[key]] || []).push(x)
      return rv
  }, {})
  
const groupByName = groupBy ('name')
const groupByEffectiveDate = groupBy ('effectiveDate')
  
const groupByNameAndFlattenAndUngroup = compose(
    flatten,
    ungroup,
    groupByName
)

const groupBygroupByEffectiveDateAndFlattenAndUngroup = compose(
    flatten,
    ungroup,
    groupByEffectiveDate
)

console.log(
  groupByNameAndFlattenAndUngroup (arrs)
)

console.log(
  groupBygroupByEffectiveDateAndFlattenAndUngroup (arrs)
)

Comments

0

You can ungroup and flatten in a single function.

const ungroupAndFlatten = (obj) => Object.keys(obj).reduce((a, k) => {obj[k].forEach(e => a.push(e)); return a}, [])

Comments

0

I did it my way, here's some code - keep in mind there's a loop inside, no recurring code:

function compose(elementary_funcs = []) {

    return function() {
        if (elementary_funcs.length > 0) {
            var currfunc = elementary_funcs[0]
            var retval = currfunc.apply(null, arguments)

            for(let i = 1; i < elementary_funcs.length; i++) {
                currfunc = elementary_funcs[i];
                retval = currfunc.call(null, retval);
            }
            return retval;
        }
    }

}

You can then just use it:

const a = a => a + 1;
const b = a => a * 2;
const c = a => a % 5;


const cmpd = compose(a, b, c);

const res = cmpd(2); // Will result in integer of value 1

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.