Functional Freeze With Ramda JS

This article shows the power of Ramda and functional programming in JavaScript by recursively freezing an object and it’s nested object references. The solution was inspired by the recursive deepFreeze function on the Mozilla Developer network Object.freeze documentation page.

The reason you would want to freeze an Object would be to make it immutable, meaning that no assignments or re-assignments could be made to the object. Object.freeze is a shallow operation however so the freeze wouldn’t apply to properties on nested objects. This feature makes perfect sense, nested objects aren’t apart of their parent object, they are different objects, one just happens to hold a referenced to the other in a nested property (but they both could have nested references to each other as well).

So remember, Object.freeze operates on the left hand of the colon. In the following example only the properties on someObj are frozen, the window object is not effected. If freeze was a deep freeze, then this code would freeze window and every object nested in window, (including someObj).

1
2
3
4
5
const someObj = {
// Freeze works on the left hand of an assignment
propOfSomeObject: "freeze me baby!",
environment: window
}

The example below is the Ramda solution. Don’t deepFreeze objects with references to themselves or anything else important without adding in extra precautions, as deepFreezing the global object will likely render an app useless until you refresh the page.

1
2
3
4
5
6
7
function deepFreeze(obj) {
R.map( // Map over own properties in `obj`
R.when( // When property is an Object
R.is(Object), deepFreeze) // deepFreeze that object
)(obj)
return Object.freeze(obj) // Finally, freeze `obj` as well
}

If you haven’t yet used Ramda, visit Ramda. Compare the above solution to the Mozilla solution below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// To do so, we use this function.
function deepFreeze(obj) {
// Retrieve the property names defined on obj
var propNames = Object.getOwnPropertyNames(obj);
// Freeze properties before freezing self
propNames.forEach(function(name) {
var prop = obj[name];
// Freeze prop if it is an object
if (typeof prop == 'object' && prop !== null)
deepFreeze(prop);
});
// Freeze self (no-op if already frozen)
return Object.freeze(obj);
}
obj2 = {
internal: {}
};
deepFreeze(obj2);
obj2.internal.a = 'anotherValue';
obj2.internal.a; // undefined

All the comments in the Mozilla code apply to the Ramda version of the deepFreeze function as well. Give the Ramda implementation a try in the RunKit below:

I don’t even recommend deep freezing as a solution to in-production immutability. You might use deepFreeze to make sure that your not mutating state prior to deployment. If you freeze your state and your state is large and has frequent changes then it could see a performance dip.. however, if your using Object.freeze and getting no TypeErrors then you could just remove it for production.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import {identity, set, lensPath} from 'ramda'
// Are we in development?
const DEVELOPMENT = true;
// only use Object.freeze in development
const toFreezeOrNotToFreeze = DEVELOPMENT ?
deepFreeze : identity
// This function is called anytime state is updated
export function updateAt(...pathParts) {
return (value, obj) => {
return toFreezeOrNotToFreeze(
set(lensPath(pathParts), value, obj)
)
}
}
// Example:
const state = {a: {b: {c:49}}}
const state2 = someStateReducer(state)
// Assertions for example only.
console.assert(state !== state2 && state2.a.b.c === 100) // pass
console.assert(Object.isFrozen(state2)) // pass
function someStateReducer(state) {
return updateAt('a', 'b', 'c')(100, state)
}