I have the example below of an impure function. Note the variable a
is outside of the function scope is being changed. To get around it, one could clone the object
Change the object:
function transformObject(a) {
Object.keys(a).forEach(function (k) { a[k] *= 2; });
}
var a = {
a : 1,
b : 2
};
transformObject(a);
document.write('<pre>' + JSON.stringify(a, 0, 4) + '</pre>');
Keep the object and return a new object with the result:
function transformObject(a) {
var b = {};
Object.keys(a).forEach(function (k) { b[k] = a[k] * 2; });
return b;
}
var a = {
a : 1,
b : 2
},
b = transformObject(a);
document.write('<pre>' + JSON.stringify(a, 0, 4) + '</pre>');
document.write('<pre>' + JSON.stringify(b, 0, 4) + '</pre>');
A pure function follows three main principles:
The function should do only one thing.
The function does not change state outside its scope.
The function outputs the same value for the same inputs.
The next step is to explore the impact of these principles in the design of the function.
Your function already meets the first principle, although the name should be improved ;)
And it meets the second principle too, because it doesn't modify anything else outside its scope (its input parameters).
Unfortunately it doesn't meet the third principle:
var a = {
a : 1,
b : 2
};
var b = transformObject(a);//b = {2, 4}
var c = transformObject(a);//c = {4, 8}
Same inputs (a
) but different outputs. That violates the third principle.
Referentially transparent functions needs immutable data.
A good answer has already been posted by Nina Scholz:
function transformObject(a) {
var b = {};
Object.keys(a).forEach(function (k) { b[k] = a[k] * 2; });
return b;
}
But it only works because the input object does not contain any other object nested in it.
Right now there are some good libraries that gives you immutable structures (like ImmutableJS).
A simple example using ImmutableJS:
describe('a List', () => {
function addMovie(currentState, movie) {
return currentState.push(movie);
}
it('is immutable', () => {
let state = List.of('Avengers', 'Antman');
let nextState = addMovie(state, 'Superman');
expect(nextState).to.equal(List.of(
'Avengers',
'Antman',
'Superman'
));
expect(state).to.equal(List.of(
'Avengers',
'Antman'
));
});
it('can be updated and returns another immutable List', () => {
let state = List.of(1,2,3,4);
let nextState = state.map(value => value*2);
expect(nextState).to.equal(List.of(2,4,6,8));
expect(state).to.equal(List.of(1,2,3,4));
});
});
You can read more about immutable API here