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:
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.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.
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.