# JavaScript: Writing a Functional Programming-Style map Function

## Info

Summary | Learn how to write a generic map function to map Arrays, Objects, and Functors. |
---|---|

Shared | 2020-02-20 |

*Many thanks to Helen Durrant for reviewing this post and offering stellar suggestions.*

In this post, we will write a functional programming-style implementation of JavaScriptâ€™s `map`

function that not only works with `Array`

but any data structure that implements a `map`

method. Such data structures are known as `Functors`

. Some examples of `Functors`

are the algebraic data types^{1}`Maybe`

and `Async`

(prior knowledge of them is not required, and out of the two, weâ€™ll only use `Maybe`

).

By the end of this post, you will:

- know how to implement a generic
`map`

function that includes functions for`map`

ping`Array`

s,`Object`

s, and`Functor`

s - understand how to use
`map`

in a variety of scenarios - know how to write a simple
`compose`

function and use composition - know how to reliably test values for their types
- have received a small introduction to algebraic data types via the
`crocks`

library

This is a big post, so buckle up! If you want to see the final product, check out this CodeSandbox: https://codesandbox.io/s/bitter-grass-tknwb.

*Note: if youâ€™re not familiar with Array.prototype.map already, check out my video on Using JavaScriptâ€™s Array.prototype.map Method or my post on JavaScript: Understand Array.prototype.map by Reimplementing It.*

*We will use the implementation of the map function in crocks as our template, so if you want to skip this article entirely, you can go and view its source.*

## Overview

- The Goal:
`map`

All the Things - Defining Our
`map`

Function `map`

an`Array`

`map`

an`Object`

`map`

a`Function`

`map`

a`Functor`

`throw`

ing Out Bad Data

## The Goal: `map`

All the Things

Today we are going to write a `map`

function that does the following:

- accepts a transformation function that takes in some argument of type
`a`

and transforms it into a value of type`b`

; i.e.,`(a -> b)`

- accepts and handles any of the following data types:

Sounds easy, right? Weâ€™ll see!

## Defining Our `map`

Function

There are some things we already know about our `map`

function:

- itâ€™s called
`map`

(yay! nailed it!) - it takes a function (
`fn`

) and then some datum (`m`

^{2})^{3} - it returns the datum as transformed by said function

Letâ€™s sketch it out:

```
const map = (fn, m) => {
// ???
}
```

Okay, itâ€™s a start. This could conceivably be used like this:

```
map(x => x.id, [{ id: 1 }, { id: 2 }]) // [1, 2]
map(x => x.id, [{ id: 'a' }, { id: 'b' }]) // ['a', 'b']
```

Note the repetition of the `x => x.id`

. Letâ€™s try pulling it out into a variable:

```
const propId = x => x.id
map(propId, [{ id: 1 }, { id: 2 }]) // [1, 2]
map(propId, [{ id: 'a' }, { id: 'b' }]) // ['a', 'b']
```

Alas, thatâ€™s not much better â€“ now weâ€™re just repeating the variable!

Instead, what if we could store our combination of function and `map`

in a variable and then use that to call with our different data? By *partially applying* the function to `map`

, we can!

```
const mapId = map.bind(null, x => x.id)
mapId([{ id: 1 }, { id: 2 }]) // [1, 2]
mapId([{ id: 'a' }, { id: 'b' }]) // ['a', 'b']
```

Nice! Now, letâ€™s go back to our sketch. Letâ€™s turn our *binary* function (which takes *two* parameters) to instead be a series of *unary* functions (which take *one* parameter^{4}).

```
const map = fn => m => {
// ???
}
```

Wow, that was easy. By default, languages like Haskell and Elm automatically curry all of their function parameters. There are ways to automate that in JavaScript, but for today, we will *manually* curry functions by using arrow functions to simulate it: `const sum = a => b => a + b`

, for example.

Lastly, on the function definition side, it would be helpful for readers of our code to understand more about the intended types. In lieu of JavaScript not having a static type checker and me not knowing TypeScript yet, weâ€™ll do this using a Haskell-style pseudo-type signature:

`map :: Functor f => (a -> b) -> f a -> f b`

And we can place that as a comment above our function:

```
// map :: Functor f => (a -> b) -> f a -> f b
const map = fn => m => {
// ???
}
```

Woah, woah, woah! Whatâ€™s all this? Letâ€™s break it down.

```
map :: Functor f => (a -> b) -> f a -> f b
-- | | | | | |
-- 1 2 3 4 5 6
```

- Can be read, â€śhas the type ofâ€ť
- Anything after
`::`

and before`=>`

in a signature is a class constraint. This says weâ€™re going to use something in the type signature that obeys the Functor Laws^{5},*identity*and*composition*. The lowercase`f`

represents what the`Functor`

will be in the signature. - Our
`map`

ping function; e.g.,`x => x.id`

, like we did above. `->`

Arrows are used in type signatures to say â€śthen returnâ€¦â€ť. In our`map`

signature, we say, â€śWe accept a function from`a`

to`b`

then return a function that accepts`f`

of`a`

and then return`f`

of`b`

â€ť. If we were summing three numbers,`sum3 :: Number -> Number -> Number -> Number`

, this would read, â€ś`sum3`

has the type of an expression that accepts a`Number`

that returns a function that accepts a`Number`

then returns a function that accepts a`Number`

and then returns a`Number`

.â€ť`f a`

says that a`Functor`

,`f`

, wraps some other type,`a`

. A concrete example of this is`[Number]`

, which is a list (or`Array`

) of`Number`

s.`f b`

says that a`Functor`

,`f`

, wraps some other type,`b`

. Why isnâ€™t it`a`

? This signifies that when we take in the`Functor`

of any type`a`

, itâ€™s totally cool if you want to change the return type inside the`Functor`

. For example, when we take`[{ id: 'a' }, { id: 'b' }]`

and use`map`

to turn that into`['a', 'b']`

, weâ€™re taking`[Object]`

(a list of`Object`

s) and turning that into`[String]`

(a list of`String`

s).

All together now! â€ś`map`

has the type of an expression where `f`

is a `Functor`

, and it accepts a function from `a`

to `b`

, then returns a function that accepts `f`

of `a`

, and then returns `f`

of `b`

.â€ť

`map`

an `Array`

Letâ€™s `map`

an `Array`

!

Remember our `Functor`

class constraint?

`map :: Functor f => (a -> b) -> f a -> f b`

Guess what? `Array`

is a `Functor`

s! How? It adheres to the laws of *identity* and *composition*:

```
// identity
[1,2,3].map(x => x) // [1,2,3]
// composition
const add10 = x => x + 10
const mult2 = x => x * 2
[1,2,3].map(add10).map(mult2) // [ 22, 24, 26 ]
// is equivalent to...
[1,2,3].map(x => mult2(add10(x))) // [ 22, 24, 26 ]
// another example of the composition law
const compose = (f, g) => x => f(g(x))
mult2(add10(2)) === compose(mult2, add10)(2) // true
// and applied back to our prior example
[1,2,3].map(add10).map(mult2) // [ 22, 24, 26 ]
[1,2,3].map(x => mult2(add10(x))) // [ 22, 24, 26 ]
[1,2,3].map(compose(mult2, add10)) // [ 22, 24, 26 ]
```

Through `map`

, `Array`

is a `Functor`

. A way to quickly determine if something is a `Functor`

is to ask, â€śDoes it implement `map`

/ is it `map`

pable?â€ť

Since we know that `Array`

is `map`

pable, we can use our `map`

function to check if the `f a`

parameter is an `Array`

and then use the build in `Array.prototype.map`

function to get from `a`

to `b`

:

```
// map :: Functor f => (a -> b) -> f a -> f b
const map = fn => m => {
if (isArray(m)) {
return mapArray(fn, m)
}
}
// isArray :: a -> Bool
const isArray = x => Array.isArray(x)
// mapArray :: ((a -> b), Array a) -> Array b
const mapArray = (fn, m) => m.map(x => fn(x))
```

Here, we use `Array.isArray()`

^{6} to see if the argument, `m`

, is an `Array`

, then we call a function, `mapArray`

, that handles the `map`

ping of the `Array`

.

*You might be thinking: why m.map(x => fn(x)) and not m.map(fn)? As you might remember from my article on re-implementing Array.prototype.map, there are a few other arguments that the native implementation of map provide, as well as some potential changes to the this keyword in your callback function scope. Instead of allowing those to pass through, we simply take the first argument, the currently iterated value, and send that to the callback function*

^{7}.

Now that weâ€™ve seen the easy way to do `map`

with `Array`

, letâ€™s see what this would look like if we felt like implementing `mapArray`

ourselves:

```
// mapArray :: ((a -> b), Array a) -> Array b
const mapArray = (fn, m) => {
const newArray = []
for (let i = 0; i < m.length; i++) {
newArray[i] = fn(m[i])
}
return newArray
}
```

Not too shabby! All we do is create a new `Array`

and set the results of calling the callback function with each item to its index in the new `Array`

and then return that `Array`

.

Do you think our `map`

function can handle an `Array`

of `Array`

s?

```
map(x => x * 2)([ [1,2], [3,4], [5,6] ])
// Array(3) [ NaN, NaN, NaN ]
```

While we can successfully iterate over the 3 items in the top-level `Array`

, our callback function canâ€™t perform operations like `[1,2] * 2`

! We need to do another `map`

on the nested `Array`

s:

```
map(map(x => x * 2))([ [1,2], [3,4], [5,6] ])
// [ [2,4], [6,8], [10,12] ]
```

Well done! What else can you `map`

? Weâ€™re now going to leave charted waters and venture into the unknown.

`map`

an `Object`

Letâ€™s say we have an `i18n`

(short for â€śinternationalizationâ€ť) object that weâ€™ve been given that has a terribly annoying issue: every translation is prefixed and suffixed with an underscore (`_`

)!

```
const i18n = {
'en-US': {
dayMode: '_Day mode_',
greeting: '_Hello!_',
nightMode: '_Night Mode_'
},
'es-ES': {
dayMode: '_Modo dĂa_',
greeting: '_ÂˇHola!_'
nightMode: '_Modo nocturno_'
}
}
```

We could manually delete each one, or we could find and replace with our text editor, or we could write a `for`

loop to do this, but because weâ€™re super awesome functional programmers, weâ€™ll try to `map`

over the `Object`

and write a function that removes the prefixed & suffixed underscores (â€¦then we copy and paste that? work with me here!).

Before we can do this, we need to see what happens when we call `.map()`

on an `Object`

:

```
i18n['en-US'].map(x => x.slice(1))
// TypeError: i18n['en-US'].map is not a function
```

Oh no! If we canâ€™t even fix the `en-US`

`Object`

, how are we supposed to fix *all* of them? Letâ€™s update our `map`

function to handle `Object`

s.

```
// map :: Functor f => (a -> b) -> f a -> f b
const map = fn => m => {
if (isArray(m)) {
return mapArray(fn, m)
}
if (isObject(m)) {
return mapObject(fn, m)
}
}
// isObject :: a -> Bool
const isObject = x =>
!!x && Object.prototype.toString.call(x) === '[object Object]'
// mapObject :: ((a -> b), { k: a }) -> { k: b }
const mapObject = (fn, m) => {
const obj = {}
for (const [k, v] of Object.entries(m)) {
obj[k] = fn(v)
}
return obj
}
```

Here, we test if something is an object by using `Object.prototype.toString`

and make sure to `.call(x)`

instead of just `.toString(x)`

, for this reason:

```
Object.prototype.toString(null)
// "[object Object]"
Object.prototype.toString.call(null)
// "[object Null]"
Object.prototype.toString([])
// "[object Object]"
Object.prototype.toString.call([])
// "[object Array]"
Object.prototype.toString.call({})
// "[object Object]"
```

We then use our new `mapObject`

function, whose signature is

`mapObject :: ((a -> b), { k: a }) -> { k: b }`

`mapObject`

takes a function from `a`

to `b`

and an `Object`

with a key(s) and some value(s), `a`

, and returns an `Object`

with a key(s) and some value(s) `b`

. In short, it maps the *values* of an `Object`

. Our `mapObject`

function is nothing more than a `for`

loop over each value returned from `Object.entries()`

! It calls the callback function with each value and returns a new object with the same key and a new, updated value.

Letâ€™s try it out:

```
const i18n = {
'en-US': {
dayMode: '_Day mode_',
greeting: '_Hello!_',
nightMode: '_Night Mode_'
},
'es-ES': {
dayMode: '_Modo dĂa_',
greeting: '_ÂˇHola!_'
nightMode: '_Modo nocturno_'
}
}
map(x => x.slice(1, -1))(i18n['en-US'])
// {
// dayMode: 'Day mode',
// greeting: 'Hello!',
// nightMode: 'Night Mode'
// }
```

Okay â€“ what about our entire `i18n`

object?

```
map(map(x => x.slice(1, -1)))(i18n)
// {
// 'en-US': {
// dayMode: 'Day mode',
// greeting: 'Hello!',
// nightMode: 'Night Mode'
// },
// 'es-ES': {
// dayMode: 'Modo dĂa',
// greeting: 'ÂˇHola!',
// nightMode: 'Modo nocturno'
// }
// }
```

Since weâ€™re dealing with nested objects, we need to use `map`

on an `Object`

inside an `Object`

. We pass a nested `map`

ping function, and our little underscore problem is gone!

`map`

a `Function`

Remember our functions `mult2`

and `add10`

from before?

```
const add10 = x => x + 10
const mult2 = x => x * 2
```

What would happen if we used those as the arguments to our `map`

function and wanted them to be automatically composed together so that we can then provide a value later?

```
map(add10)(mult2) // undefined
map(add10)(mult2)(12) // TypeError: map(...)(...) is not a function
```

Time for our `map`

function to handle a `Function`

as the second argument and `compose`

the two functions together:

```
// map :: Functor f => (a -> b) -> f a -> f b
const map = fn => m => {
if (isArray(m)) {
return mapArray(fn, m)
}
if (isObject(m)) {
return mapObj(fn, m)
}
if (isFunction(m)) {
return compose(fn, m)
}
}
// isFunction :: a -> Bool
const isFunction = x => typeof x === 'function'
// compose :: ((b -> c), (a -> b)) -> a -> c
const compose = (f, g) => x => f(g(x))
```

And when we run our previously failed code again,

```
map(add10)(mult2) // function compose(x)
map(add10)(mult2)(12) // 44
```

we can see that calling `map`

with two functions returns a composition of those two functions, and calling that result with a primitive value (`12`

) gives us back our result, `44`

.

`map`

a `Functor`

When we learned about `map`

ping `Array`

s before, we learned that `Array`

s are `Functor`

s because they adhere to the laws of *identity* and *composition*; i.e., they are `map`

pable.

There are all sorts of other data structures that implement a `map`

method, just like `Array.prototype`

does, and we want to be able to handle those, too!

We currently have all the tools required to implement `map`

for `Functor`

s without even knowing how they might work! All we need to know is, â€śDoes it implement `map`

as a `Function`

?â€ť Letâ€™s see what we can come up with!

```
// map :: Functor f => (a -> b) -> f a -> f b
const map = fn => m => {
if (isFunction(m)) {
return compose(fn, m)
}
if (isArray(m)) {
return mapArray(fn, m)
}
if (isFunctor(m)) {
return mapFunctor(fn, m)
}
if (isObject(m)) {
return mapObj(fn, m)
}
}
// isFunction :: a -> Bool
const isFunction = x => typeof x === 'function'
// isFunctor :: a -> Bool
const isFunctor = x => !!x && isFunction(x['map'])
// mapFunctor :: Functor f => ((a -> b), f a) -> f b
const mapFunctor = (fn, m) => m.map(fn)
```

That is surprisingly simple, isnâ€™t it? We use our `isFunction`

check from before to test if `m`

has a `map`

property that is a `Function`

, then we call `map`

on `m`

and pass it the callback `Function`

in `mapFunctor`

.

You might be thinking that `mapArray`

and `mapFunctor`

could use the same handler because `Array`

s are `Functors`

, and you are correct; however, because of the extra implementation bits that come back from `Array.prototype.map`

, weâ€™ll keep them separate and only call the callback to `Array.prototype.map`

with the currently iterated item. Hereâ€™s the difference:

```
// mapArray :: ((a -> b), Array a) -> Array b
const mapArray = (fn, m) => m.map(x => (fn(x))
// mapFunctor :: Functor f => ((a -> b), f a) -> f b
const mapFunctor = (fn, m) => m.map(fn)
```

If you donâ€™t care about this, itâ€™s totally acceptable to not include the `Array`

bits at all and use the `Functor`

`map`

^{8} to handle the `map`

ping of `Array`

s, since theyâ€™re `Functor`

s.

To test our `Functor`

`map`

ping, weâ€™ll use crocks to provide us access to an algebraic data type called `Maybe`

.

```
import { compose, option, prop } from 'crocks'
const company = {
name: 'Pearce Software, LLC',
locations: [
'Charleston, SC, USA',
'Auckland, NZ',
'London, England, UK'
]
}
prop('foo', company) // Nothing
prop('locations', company) // Just [String]
option([], prop('foo', company))
// []
option([], prop('locations', company))
// [
// 'Charleston, SC, USA',
// 'Auckland, NZ',
// 'London, England, UK'
// ]
const getLocations = compose(option([]), prop('locations'))
getLocations(company)
// [
// 'Charleston, SC, USA',
// 'Auckland, NZ',
// 'London, England, UK'
// ]
```

Pump the breaks! Whatâ€™s all this `Just`

and `Nothing`

stuff? Weâ€™re not going to focus on `Maybe`

s today^{9}, but the short version is that the `locations`

property *may* or *may not* be present in the object, so we encapsulate that uncertainty inside of a `Maybe`

algebraic data type via the `prop`

function, and we provide a default value via the `option`

function that the `Maybe`

can fall back to in the event of not being able to find `locations`

.

Why does this matter? We want to `map`

a `Maybe`

, and the `prop`

function will give us access to one. Letâ€™s see what it looks like:

```
import { compose, option, prop } from 'crocks'
const upcase = x => x.toUpperCase()
const getLocations =
compose(option([]), map(map(upcase)), prop('locations'))
getLocations({}) // []
getLocations(company)
// [
// 'CHARLESTON, SC, USA',
// 'AUCKLAND, NZ',
// 'LONDON, ENGLAND, UK'
// ]
```

Okay, cool! But why are we `map`

ping twice?

When we work with algebraic data types like `Maybe`

, instead of writing `if (dataIsValid) doSomething`

, the `map`

method on a `Maybe`

gives us access to the value inside the `Maybe`

(our `locations`

), but it does so *only if the data is available*.

Once we have access to the `locations`

, we then use `map`

again to uppercase each location.

`throw`

ing Out Bad Data

What happens if the arguments passed to `map`

arenâ€™t a `Function`

and a `Functor`

?

```
map(null)([1,2,3]) // TypeError: fn is not a function
map(x => x * 2)(null) // undefined
map(null)(null) // undefined
```

I think we can provide some more helpful messaging to guide users of our `map`

tool on how to use it correctly.

```
// map :: Functor f => (a -> b) -> f a -> f b
const map = fn => m => {
if (!isFunction(fn)) {
throw new TypeError(`map: Please provide a Function for the first argument`)
}
// ...our other handlers...
throw new TypeError(`map: Please provide a Functor or Object for the second argument`)
}
map(null)([1,2,3]) // TypeError: map: Please provide a Function for the first argument
map(x => x * 2)(null) // TypeError: map: Please provide a Functor or Object for the second argument
map(null)(null) // TypeError: map: Please provide a Function for the first argument
```

Now, when we provide bad arguments, weâ€™re told exactly what we need to do.

## Wrapping Up

Congratulations and thank you for making it to the end! If you want to play around with what we created, check out this CodeSandbox: https://codesandbox.io/s/bitter-grass-tknwb.

Here is our code from today in its entirety:

```
const { compose, option, prop } = require('crocks')
// map :: Functor f => (a -> b) -> f a -> f b
const map = fn => m => {
if (!isFunction(fn)) {
throw new TypeError(`map: Please provide a Function for the first argument`)
}
if (isFunction(m)) {
return compose(fn, m)
}
if (isArray(m)) {
return mapArray(fn, m)
}
if (isFunctor(m)) {
return mapFunctor(fn, m)
}
if (isObject(m)) {
return mapObj(fn, m)
}
throw new TypeError(`map: Please provide a Functor or Object for the second argument`)
}
// we're opting for crocks' compose, instead
// compose :: ((b -> c), (a -> b)) -> a -> c
// const compose = (f, g) => x => f(g(x))
// isArray :: a -> Bool
const isArray = x => Array.isArray(x)
// isFunction :: a -> Bool
const isFunction = x => typeof x === 'function'
// isFunctor :: a -> Bool
const isFunctor = x => !!x && isFunction(x['map'])
// isObject :: a -> Bool
const isObject = x =>
!!x && Object.prototype.toString.call(x) === '[object Object]'
// mapArray :: ((a -> b), Array a) -> Array b
const mapArray = (fn, m) => {
const newArray = []
for (let i = 0; i < m.length; i++) {
newArray.push(fn(m[i]))
}
return newArray
}
// realistically, you should use this mapArray:
// const mapArray = (fn, m) => m.map(x => fn(x))
// mapObj :: (a -> b) -> { k: a } -> { k: b }
const mapObj = (fn, m) => {
const obj = {}
for (const [k, v] of Object.entries(m)) {
obj[k] = fn(v)
}
return obj
}
// mapFunctor :: Functor f => ((a -> b), f a) -> f b
const mapFunctor = (fn, m) => m.map(fn)
```

Thank you for reading!

Robert

https://github.com/hemanth/functional-programming-jargon#algebraic-data-type â€‚â†©ď¸Ž

Wondering why the data comes last? Check out Brian Lonsdorfâ€™s â€śHey Underscore, Youâ€™re Doing It Wrong!â€ť talk. The tl;dr is that you should arrange your arguments from least likely to change to most likely to change in order to pave the way for partial application and greater code reuse. â€‚â†©ď¸Ž

https://github.com/hemanth/functional-programming-jargon#arity â€‚â†©ď¸Ž

https://github.com/hemanth/functional-programming-jargon#functor â€‚â†©ď¸Ž

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray â€‚â†©ď¸Ž

Check out ramda.jsâ€™

`addIndex`

function to see a different pattern for working with indices and`Array`

s. â€‚â†©ď¸ŽIf youâ€™re an egghead.io subscriber, Andy Van Slaars has a great course, Safer JavaScript with the Maybe Type, or you can check out a Haskell article on The Functor class. â€‚â†©ď¸Ž