Before you go, check out these stories!

0
Hackernoon logoUnderstanding Currying, Closures, and Coupling in JavaScript by@sambernheim

Understanding Currying, Closures, and Coupling in JavaScript

Author profile picture

@sambernheimSam Bernheim

Software Engineer @ Disney Streaming Services

Here's a problem. You have an object in your code exposed via an

export
. You also have a function in the same file (also exported) consuming that object directly.

const info = {
    name: 'sam'
    age: 23,
    city: 'New York City'
}

function printInfo() {
    console.log(info)
} 

export {
    info,
    printInfo
}

Working in javascript, a beautiful wild west, once the object is exposed to the outside world, anything can happen. One file might import it to read its data, another might mutate it, yet another could try to restore it. Caught in the middle of this frantic struggle is your lonely function, alone, forgotten, and afraid. When inevitably invoked, the object it depends on may have changed beyond recognition causing a runtime failure.

How can we prevent such mutations and manipulations from persisting and guarantee that our function,

printInfo
, can run with peace of mind?

The easy way out would be to wrap the object in a function and have all other references invoke the function to get a new, crisp, clean, copy. To prevent this solution, let's make the problem more contrived! We want mutations to persist across the lifecycle of our application with one exception! Our function should always consume the object in its original untouched state.

Our goals:

  • Expose the object as just an object without wrapping it in a function.
  • Ensure our function is able to use the object with a guarantee that external manipulations don't carry through via the pass by reference mechanism.

Faced with this problem at work, I looked to currying for an elegant solution.

const wrappedPrintInfo = (obj) => () => console.log(obj);

export const printInfoTwo = wrappedPrintInfo(obj)

Create a function called

wrappedPrintInfo
that accepts an object as its parameter and returns a function which when invoked logs that object. Then keep this wrapped version private to the file using it to generate our printInfo function which we then expose as normal.

As always, nothing works on the first try. The object is still passed by reference even through the currying and changes to

obj
in one part of your application will persist through
printInfoTwo
. Unfortunately there's no getting around the pass by reference issue with just a curried function.

The curried function must make its own internal copy.

const wrapedPrintInfo = (obj) => {
    const copied = Object.assign({}, obj);

    return () => {
        console.log(copied);

    }
}

const printInfoThree = wrappedPrintInfo(obj)

Now we still use a curried function but inside the first layer we use

Object.assign
to create a copy of the passed in object and use this copy in the returned function. Effectively we use a closure to create a private copy of the passed in object and then return the real function in the closure.

Why Would You Ever Do This

If you don't own the exported object completely, other parts of an application may rely on it and potentially its multiple mutations. Wrapping the object in a function may lead to some unintended consequences that could break part of your application. This approach protects against those potential regressions.

Currying the function and using a closure to make a copy of the object allows for the function to only runs once when first creating the

printInfo
function. We also don't have to rewrite a potentially massive object in our function's scope leaving our code DRY.

Finally, since this is a curried function, should we ever need to reuse it for another object, we can do so easily with the same benefits by exposing the wrapper and currying with any other object.

The biggest benefit is a clear separation of concerns where two parts of our app remain completely untangled.

Conclusion

Doing all this to achieve a console.log is quite boring but the console log is an abstraction over what could be a far more complex set of operations. This curried function is completely agnostic to the rest of the application and completely decoupled.

Should another part of the application require the same function to run on a different object, regardless if that object is exposed, this function guarantees that the object passed in will never be modified by any other external source. It can be expanded to curry other functions, or be partially applied to other objects. Our previously timid function now stands strong and secure, guaranteeing changes elsewhere won't affect it. This isn't always the right thing to do, but demonstrates another interesting combinations of architectural principles.

Tags

Become a Hackolyte

Level up your reading game by joining Hacker Noon now!