DEV Community

John Kazer
John Kazer

Posted on

Don't be confused by closures, currying etc

If you are worrying about the how and the detail, just relax. Focus on the logical operations.

The fundamental issue of a functional style is to deal in the logic of data transforms. You don't need to know how currying works, just use it (e.g. via a library like Ramda). Avoid the books and tutorials that try to explain the technical features and give examples of functions implementing partial application - at least for now.

If you are used to a more imperative style where the how is important, such as the detailed structure of a for loop, then it can be confusing dealing with the details of closures, currying, partial application and such like. There are many resources telling you in detail how these things work. But functional programming is about logic and data manipulation. You don't have to worry about the detail, so don't! Understanding what these tools can do is important, details less so.

Write pseudo-code and re-use or write functions to match.

The first time I really felt I "got" the concept of currying wasn't by reading examples explaining how to build a curry or partial application function from scratch. It was just using the concept to solve a simple problem. Now, I won't say that the following problem or solution are good practice but that doesn't matter either :-)

A simple example in pseudo-code:

  • create a basic button so the app is usable
  • save a way to update it later
  • render the basic button
  • delay here whilst getting some info from the user
  • eventually get the information to update the button
  • use my saved method to make the update
  • optionally do this again, with the exact same functions but different data

The key feature here is the delay in getting some data then being able to apply it to make a change. A bonus is being able to repeat this again, reliably. When there is a delay, use currying (or possibly a closure) to retain functionality or data for later.

import { curry } from 'ramda'

/**
 * return { HTMLButtonElement }
 */
const updateButton = curry((/** @type { HTMLButtonElement } */ button, /** @type { string } */ label) => {
    return button.textContent = label
})
/** @type { HTMLButtonElement } */
const buttonToUpdate = document.getElementsById('button')
renderBasicButton(/** @type { HTMLButtonElement } */ buttonToUpdate, 'dummy label') // create the button with an initial label
// generate a new function
renderButtonWithLateLabel = updateButton(/** @type { HTMLButtonElement } */ buttonToUpdate) // use currying to store the ability to update the button later
// renderButtonWithLateLabel is now a function that has stored the button. It's just waiting for the second argument, the string, to come along later.

// do stuff, get a string from the user maybe

/** @type { string } */
const labelDefinedLater = "text"
renderButtonWithLateLabel(/** @type { string } */ labelDefinedLater)
// now we complete the logical step by updating the "saved" button with a new label

So a toy example but it was all I needed to grasp the principle of currying. It's all about delayed functionality.

Another more useful example is managing the values in the objects within an array. Say I wanted to update a value 'good' in a specific object (e.g. with a specified value of 'key'), whilst retaining the overall array integrity. This could be quite a complex process of mutated data, nested loops, searching through each object, finding the one to change, making the change and rebuilding the array.

However, I did a google search with a phrase that mirrors the general logic I was after "update value in an object array ramda" and the first result is what I want.

import { curry, map, when, propEq, assoc } from 'ramda'

const alter = curry((checked, key, items) => map(
  when(propEq('key', key), assoc('good', checked)),
  items
))

const arrayWithUpdatedObject = alter('true', '22', items)
// in the array "items", find the object with 'key' is 22 and set 'good' to be true

Which is fine, but is a rather one-shot sort of thing. If I make the first argument to propEq an argument to the curried function it becomes more interesting. By making an additional argument, we can save an aspect of functionality to use later.

const alter = curry((alterBy, checked, key, items) => map(
  when(propEq(alterBy, key), assoc('good', checked)),
  items
))

const alterByKey = alter('key') // save the concept of matching the objects by key

const arrayWithUpdatedObject = alterByKey('true', '22', items) // will alter the object with key === '22'

// alternatively you could create a separate instance which identifies objects to alter by id
const alterById = alter('id')

In many ways a closure is similar to currying. You are keeping some data or functionality around to be used later. The "how" of creating a closure isn't really important - the key thing is it's usefulness in your logical process.

Here is a great example of using a closure to store data and functionality for later. A function is created called person that contains data (firstName, lastName) and functions (getFullName, setFullName) which are stored for later use.

Thanks to

stopachka image

Top comments (0)