2

I'd like to understand how the immutable methods of lodash/fp work.

Do they deeply clone a collection before changing it or do they implement kind of a structural sharing between the two versions of the object?

I have tried to understand it "from the outside" and I couldn't come to a clear conclusion.

In the first case below, mutating an updated collection doesn't impact the original collection.

However, in the second case, mutating an updated collection does impact the original collection.

var fp = _.noConflict();

var data = { a: { c: {} } };
var updatedData = fp.set(["a", "c", "d"], 5, data);
updatedData.a.c.e = 9;
console.log(updatedData.a.c.e, data.a.c.e);

var data2 = { a: { c: [] } };
var updatedData2 = fp.set(["a", "d"], 5, data2);
updatedData2.a.c[0] = 9;
console.log(updatedData2.a.c[0], data2.a.c[0]);
<script src='https://cdn.jsdelivr.net/g/lodash@4.17(lodash.min.js+lodash.fp.min.js)'></script>

I have also posted the question on Lodash github.

viebel
  • 19,372
  • 10
  • 49
  • 83
  • Could you tell me what makes my snippet not minimal ? – viebel Nov 29 '20 at 01:20
  • oh, sorry, missread the question ... – Jonas Wilms Nov 29 '20 at 01:24
  • If I understand [the source](https://github.com/lodash/lodash/blob/npm/fp/_baseConvert.js) correctly, Lodash clones all parameters of the called function, so your first example barely equals `_.set(["a", "c", "d"], 5, _.cloneDeep(data))` – Jonas Wilms Nov 29 '20 at 01:35
  • If there were a cloneDeeep, then in the second example data.a.c[0] should have been 9 – viebel Nov 29 '20 at 02:06
  • 1
    I don't know lodash but a tree (formed by objects) can share structure by just copying the path from root to updated leaf and thus obtain immutability efficiently. You cannot do this with an array. The deeper reason for this difference is due to the fact that trees are recursive and thus algebraic data structure, whereas arrays are not. –  Nov 29 '20 at 10:26
  • @viebel I have edited your question to be an executable snippet. Running it shows that the output is always 9 in all cases, not `underfined`. Could you edit the question again to show what the problem was? I suspect that you just had a typo before. – Buh Buh Nov 29 '20 at 11:24
  • Thank you so much @BuhBuh. I have modified the snippet so that it logs undefined. – viebel Nov 29 '20 at 17:21

1 Answers1

1

Do they deeply clone a collection before changing it or do they implement kind of a structural sharing between the two versions of the object?

Yes, they use a structural sharing.

We can see in this example that the c object is reused, not cloned. This is useful because it could be very large in size (in my case it contains the entire window object!).
You can imagine attempting to deep-clone it would be a waste of resources.

var fp = _.noConflict()
var data, updatedData


data = { a: { c: { window: window } } }
updatedData = fp.set(["a", "d"], 5, data)
console.log(updatedData.a.c === data.a.c)
<script src='https://cdn.jsdelivr.net/g/lodash@4.17(lodash.min.js+lodash.fp.min.js)'></script>

Only the "parent" objects are being changed, so they must be cloned. The leaves which are not part of the mutation do not need to be changed.

The reason it has gone wrong for you in your example is because you are combining immutable and mutable code, which is not a good idea. These cloning optimisations will obviously go wrong if you mutate code that was expected to be immutable.

Buh Buh
  • 7,443
  • 1
  • 34
  • 61