I'm very new to writing immutable code. Would there be any fundamental issues trying to transpile arbitrary mutable code person.age = 20 into immutable code person = { ...person, age: 21 }?

I am aware that it would change equality, such that "oldReferenceToPerson != newReferenceToPerson" after a change.


A comment rightly pointed out that my "immutable" code does mutate the person. I meant the above strategy of replacing objects instead of mutating them.

  • 1
    Only that (1) it doesn't necessarily eliminate mutability since the pre-existing variable person is updated, and (2) how should it know to change constant 20 to 21? – Erik Eidt Dec 16 '20 at 22:38

No, this is not possible in the general case since the data dependencies in a system can be very tricky.

As a simple example, consider the following setting:

var person = {...};
var someOtherObject = {p: person};

We have two references to the shared object: person and someOtherObject.p. A modification person.age = 20 would be reflected through both references. A reassignment person = {...person, age: 20} would leave stale data in someOtherObject.

In this simple case, detecting the dependency might be feasible. But in the general case, the transpiler would have to solve the halting problem – impossible.

In languages other than JavaScript, this can become possible. E.g. Rust strictly limits what references can be active at the same time. There cannot be any other reference when there is a mutable reference active.

Within JavaScript, such transformations might be possible for those cases where the transpiler can prove that there won't be a conflict. However, such cases will be limited to very simple cases where the object being modified is created very closely, as JavaScript has reference semantics and no concept of exclusive ownership. This transformation is potentially more difficult than type inference, which is difficult enough (read: also impossible) for vanilla JavaScript.

  • 1
    I have a solution to your example problem, but I'm not sure if it checks out. A transpiler could replace both person and someOtherObject.p with an immutable ID (say, a UUID). This ID could be used to perform a lookup into a global (immutable, append-only) data structure which maps UUIDs to immutable values. You would replace the in-place mutation of the person object, with a new-but-modified person object. You would then update the global data structure to associate the same old UUID to the new person value. – Alexander Dec 17 '20 at 18:55
  • @Alexander Yes that would technically work, by introducing an extra layer of indirection. Replacing references with IDs is a common strategy to simulate pointers. But I would argue we effectively still end up with mutable objects: whereas the original approach mutates an object that is referenced by the variables, we now update a lookup table that is (indirectly) referenced by the variables – all very functional, but the only advantage seems to be “purity” (in the Haskell sense). – amon Dec 17 '20 at 19:13
  • "But I would argue we effectively still end up with mutable objects" Agreed, it's immutable by a technicality. Then again, I took exception to many of the claims of immutability made e.g. by the functional programming community. IMO, an assembly of locally-immutable operations can be de-facto mutable. If I replace every = to a field with a return of a modified copy, it's technically immutable, but in practice it's a mutation that happened. You have an old object that you discard, and a new one that replaces it. Object IDs are different, but its "identity" in a mental model stays the same. – Alexander Dec 17 '20 at 19:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.