Map, flip(map) and partial application in JS

dbillinghamuk
4 min readDec 19, 2018

An in depth example of partially applying functions, composing map and inverting function arguments.

Introduction

Please see Partial Application and map post for an introduction. It’s relatively easy to get started with these core concepts. But the real power comes when composing them together. This post uses ramda and explores some of the ways functions can be extracted.

Scenario

I have a function, which I want to apply to each property in an array, nested within an object. In this scenario I am not interested in using lenses to return an updated root object, I just want an array of items with my executed function.

const prependVal= val => 'val: ' + prop('name')(val)const item = {
subItems: [{
name: 'sub item one'
},{
name: 'sub item two'
}]
}

Output:

[{
name: 'val: sub item one'
},{
name: 'val: sub item two'
}]

Approach 1

This is a fairly easy problem to solve using a couple of standard functional programming functions.

compose(map(prependVal), propOr([], 'subItems'))(item)

Approach 2

But what if we wanted to extract it out into its own function, say applyFnToSubItems. We could write something like this.

const applyFnToSubItems = fn => compose(
map(fn),
propOr([], 'subItems')
);
//calling
applyFnToSubItems(prependVal)(item)

This is okay, and I like the way it is called, but I don’t like the fact that we have to scope the fn variable and explicitly call it against map.

Approach 3

It would be nice if we could have a function that looked more like this:

const applyFnToSubItems = compose(
map,
propOr([], 'subItems')
);
//calling
applyFnToSubItems(prependVal)(item)

Although, this isn’t going to work due to the fact that the first function within compose will get executed against the function that we want applied to each element in the array, (prependVal).

What we want to do is partially apply the function we want executed, (prependVal), against map.

const applyFnToSubItems = compose(
map
);
//calling
applyFnToSubItems(prependVal)(item)
//output {"subItems": "val: undefined"}

map is now a function expecting an array, but we are passing it the item object. So we need to filter the second curried item object to get out the subItems. We were filtering the subItems previously, so let’s just add that back to our compose.

const applyFnToSubItems = compose(
propOr([], 'subItems') //isn't going to work
map
);

But this isn’t going to work as we are executing the propOr function on what’s being returned from map, which is a function, (as it has only been partially applied).

What we need is to scope the partially applied function, and return another function, that will get passed the item object.

const applyFnToSubItems = compose(
partiallyAppliedMap =>
item =>
partiallyAppliedMap(propOr([], 'subItems')(item)),
map
);

This will work fine, but it’s not looking that elegant at the moment.

We could extract that additional functionality into its own function, which would also work.

const thing = fn => partiallyAppliedMap => item => 
partiallyAppliedMap(fn(item));
const applyFnToSubItems = compose(
thing(propOr([], 'subItems')),
map
);

But instead of creating our own thing function we can take advantage of map.

Approach 4

Usually we think of map to iterate over a list of values, and apply a function. But we don’t have to, map can be applied to other things (functors), including other map functions, meaning they can be chained.

map(i => {
console.log('1:', i);
return i;
})(i => {
console.log('2:', i);
return ({...i, a: 1});
})(item);
//output
2:
{"subItems":[{"name":"sub item one"},{"name":"sub item two"}]}
1:
{"subItems":[{"name":"sub item one"},{"name":"sub item two"}],"a":1}
{"a": 1, "subItems": [{"name": "sub item one"}, {"name": "sub item two"}]}

We can think of our example as having 3 inputs:

  1. The item object
  2. The prependVal function
  3. The propOr([], ‘subItems’) function

So maybe we could do something like this:

map(prependVal)(propOr([], 'subItems'))(item)

But this will error given the the final map call will pass an array, and prependVal does not accept an array, it accepts objects of the array. Meaning that we have to map over the array.

map(map(prependVal))(propOr([], 'subItems'))(item)

That’s fine, but applying this back to our example, the function calls are the incorrect way around. It needs to look something more like this:

map(propOr([], 'subItems'))(map(prependVal))(item)

But this just isn’t going to work, we need to flip the map function so that the functions can be added in this order. For this we can use flip. By executing the flipped map function, we accept the second argument first, and the first argument second.

flip(map)(propOr([], 'subItems'))(map(prependVal))(item)

Okay that now works again. But how can we apply this to our applyFnToSubItems function? Let’s start by pulling bits out into our compose function. First we’ll move flip(map)(propOr([], ‘subItems’))

const applyFnToSubItems = compose(
flip(map)(propOr([], 'subItems'))
);
//calling
applyFnToSubItems(map(prependVal))(item)

And then that final map

const applyFnToSubItems = compose(
flip(map)(propOr([], 'subItems')),
map
);
//calling
applyFnToSubItems(prependVal)(item)
//output
[{
name: 'val: sub item one'
},{
name: 'val: sub item two'
}]

This final example looks very similar to when we introduced our thing function in Approach 3, and it is essentially doing the same thing as flip(map) in this example.

Conclusion

For me Approach 4 is the most elegant. But I definitely think that attacking tasks like this in a couple of different ways, helps to improve knowledge and cement understanding.

--

--

dbillinghamuk

Software dev — Javascript, node, express, mongo, react, redux, rxjs, es6, ramda