Safety of Belt methods

Recently, I had to clone a nested array before I did some dangerous mutations. In my naivety, I thought Belt’s copy would do a deep clone. As I found out that it mutated the state of my reducer, I checked the source and saw that it is just an external binding to Array.prototype.slice (and thus only doing a shallow clone).

So what would be the right way to improve that? Add a copyDeep method and just document that copy only does a shallow copy. Or would it be better if the default copy was the deep one?

1 Like

I’m not sure if it’s possible to have a polymorphic/generic deepCopy work in a type-safe way. The “depth” of a nested array is part of its type signature, array<int> vs array<array<int>> for example.

(Feel free to correct me if I’m wrong.)

But if you know the depth of your array (which you probably should) then it’s simple to write a size-specific deep-copy function:

// copy 2 levels deep
let deepCopy2 = a => Belt.Array.map(a, Belt.Array.copy)
// copy 3 levels deep
let deepCopy3 = a => Belt.Array.map(a, deepCopy2)
// copy 4 levels deep
let deepCopy4 = a => Belt.Array.map(a, deepCopy3)

I agree that a note about this would be a good addition to the documentation, though.

1 Like

Yeah that’s how I solved it for my case.

I agree a Belt method would probably need to bail out of the type system (or even use raw) for it, but that would be hidden from the user as the signature is still array<'a> => array<'a>.

At the end of the day, array is still a mutable type. So if you have an array of arrays, you have an array of mutable values.

One way to solve this could be to introduce an immutableArray type: even if it’s just a regular JS array underneath, you’d only have immutable functions to work with it. You’d have to copy on conversion to/from regular arrays though.

As for shallow copies, I think that’s the idiomatic immutable approach. It only becomes a problem when you mix mutable and immutable code (which in practice may happen a lot, of course).

2 Likes