Currying in Javascript: How & Why?

July 18, 2017 0 Comments

Currying in Javascript: How & Why?

 

 

Image cropped from ‘Curry base 1: spices’ by Hildgrim on Flickr and used under the terms of the photo’s creative commons license.

A recurring issue in many programming projects is the need inject context into a function in a clean and tidy way.

Let’s say you are writing a series of widget extractors that use slightly different extraction rules for each kind of widget, but the actual core extraction code is identical given these rules.

You might write

const extractShape => (data, rules) =>
applyRule(data, rules.shape)
const extractDimensions => (data, rules) =>
applyRule(data, rules.dimensions)

then in your app code you might have

const popuateWidget = (data, rules) => ({
shape: extractShape(data, rules),
dimensions: extractDimensions(data, rules)
})

and somewhere higher up in the stack you have an Express controller function

const getWidgetDetails = (req, res) => {
const { id, type } = req.body
const data = lookupData(id)
const rules = lookupRules(type)
return res.json(populateWidget(data, rules))
}

The data and rules are context, and that context is being passed around all over the place.

const widgetPopulator = (id, type) => {
const data = lookupData(id)
const { shape, dimensions } = lookupRules(type)
const extractShape = () => applyRule(data, shape)
const extractDimensions = () => applyRule(data, dimensions)
  return {
shape: extractShape(),
dimensions: extractDimensions()
}
}
const getWidgetDetails = (req, res) => {
const { id, type } = req.body
return res.json(widgetPopulator(id, type))
}

That’s nicer but let’s say for example you are now asked to write an Express controller that returns an array of populated widget details, given an array of ids and a single type.

The naive approach would be simple to do this

const getAllWidgetDetails = (req, res) => {
const { ids, type } = req.body
const widgets = ids.map(id => widgetPopulator(id, type))
return res.json(widgets)
}

But what if lookupRules involves a 3rd party API call, or some other expensive process? What we really want is a widgetPopulator function that’s specific to a type.

const widgetPopulator = (type) => {
const { shape, dimensions } = lookupRules(type)
return (id) => {
const data = lookupData(id)
const extractShape = () => applyRule(data, shape)
const extractDimensions = () => applyRule(data, dimensions)
    return {
shape: extractShape(),
dimensions: extractDimensions()
}
}
}
const getWidgetDetails = (req, res) => {
const { id, type } = req.body
return res.json(widgetPopulator(type)(id))
}
const getAllWidgetDetails = (req, res) => {
const { ids, type } = req.body
const populatorForType = widgetPopulator(type)
const widgets = ids.map(id => populatorForType(id))
return res.json(widgets)
}

The key difference here is now our widgetPopulator function returns a function that already has its rules baked in, and so don’t need to be looked up again, and so the type does not need to be passed in repeatedly.

It may seem trivial but if lookupRules is an expensive function then we really want to minimise the number of times it’s invoked.

This process of injecting context is known as currying and it’s one of the most useful tools in your Javascript toolbox.


Tag cloud