Replace `.toSorted`, `.toSpliced`, `.toReversed` with `.clone`

I was no here when that proposal was wrote. But those methods are new and we still have time to fix this.

The change array by copy proposal is harmful for JS/TS.

Since its release, the learning curve for splice, sort and reverse got duplicated by their unmutable contraparts.

What will happen if we want to add a new "self-mutable" method to arrays in the future? Will we need to create an unmutable method too? How much methods we really want within our arrays?

Also, those methods are no self-descriptive at all, at first sight you won't know that are copying their objects.

I'd like to replace those redundant methods with a single one called .clone.

This method will (of course) clone the array (or tuple) to avoid modify the original one.

[1, 2, 3, 4].clone().reverse() // [4, 3, 2, 1]

If we want to clone AND MODIFY in a single step (avoid clone and then modify), has the involved proposal does, then we could add an argument that will accept the action to perform:

.clone(Array.prototype.reverse) or .clone("reverse") to create a new array with their items reversed.

["Hi", "how", "are", "you", "?"].clone("reverse") // ["?", "you", "are", "how", "Hi"]

Related proposal: tc39/proposal-change-array-by-copy: Provides additional methods on Array.prototype and TypedArray.prototype to enable changes on the array by returning a new copy of it with the change. (github.com)

We won't be adding any more mutating array methods in the future.

The array methods you're complaining about have already shipped and can't ever be unshipped at this point.

1 Like

We can deprecate those methods on reason of .clone(). I think that just because are shipped that does not means that are permanent

@claudiameadows

The .clone() method can handle any previous mutable array method created and wfuture new ones as is described in the post, without the needs of create non-mutable copy of the methods that currently exists, and without force developers to learn new methods every time

Deleted my post as I initially misinterpreted some things.

What's the difference this is supposed to provide over just providing new immutable methods? I don't see why two methods instead of one for a new logical mutable method is a problem.

Shipped things on the web are almost always permanent.

More methods is often better - it doesn’t matter if there are a thousand array methods, as long as they’re all independently motivated - and that’s a slippery slope argument anyways, since we’re unlikely to even get to 50.

I'm agree with you @ljharb, the number of methods does not matters, but this is just if you're not duping some funtionality; sort, splice and reverse in this case.

The new methods are not healty for the JS ecosystem. As the people who did propose that, i want to clone arrays and even modify it as same time i'm cloning it, i do love performance

@claudiameadows

But we have the following problems with those 3 methods:

1 - As I said many times, are redundant
2 - Are not self-descriptive
3 - Increases the learning curve of JS unnecessarily
4 - Does not respect the DRY principle
5 - Are very limiting

Those kind of methods suppose that array copy requirement is just for those functionalities... Could not be farther from the truth. So much people (i donno who tho xD) would like has the array copy method for their own algorithms.

I'll argument this last point with an example:

I read here that someone proposed the Array.prototype.rotate method. For performance reasons for sure this kind of method will mutate its object. This method won't be accepted (since I know), then supossing that I want that functionality and I do code it by myself, what can i do if I need to not change the original array content at some moment?

Why the sort, splice and reversed methods has the capacity to clone an array but the whole JS ecosystem does not?

We need an standarized way to copy objects and mutate it if is required

const array = [1, 2, 3];

array.clone(myOwnRotateLogic); // [2, 3, 1] (rotated by 2, just an example)
                               // array: [1, 2, 3]

const clonedArray = array.clone(Array.prototype.reverse) // [3, 2, 1]
clonedArray.clone(sortLogic) // [1, 2, 3]
                             // clonedArray: [3, 2, 1]

Really? I thought I saw somewhere that engine implementers say that this is already slowing down method lookup?

This would be slower than toReversed.
The new methods are faster than doing .slice().reverse() because the reversing can be done as part of copying the array so there is only one iteration, this better utilizes CPU caches.

If a method is passed in it would need to operate after the copy.

2 Likes

This is the same reasoning behind why I'd like to add JSON.equals(x, y) as an internalized synonym for JSON.stringify(x) === JSON.stringify(y) - if the two JSON trees are walked simultaneously, the engine doesn't have to do as much work or allocate as much memory. (Not to mention it offers an obvious fast-path escape hatch if x === y)

The idea of have a .clone method is to avoid 2 steps when you create a new copy of the array, i did explain it in this post.

If we are worried about Js performance, the good path is to create a new method that will improve the copy speed of the arrays which comes from "self-mutable" methods for everyone, and not just for some 3 native methods.

Could you explain how an implementation of clone would not contain 2 steps? Perhaps with pseudocode

Yeah of course

We could have something like: .clone() dummy implementation. (Deno).

Thanks! With that approach it doesn't look like it would be possible for someone to pass in Array.prototype.reverse as the transform.

I'm a little confused - you don't seem to like the idea of having so many functions, but your example implementation introduces a whole new set of functions, one for each type of transformation.

Thats not the point, that never was the point. Does not matters if you can pass the Array.prototype.reverse function reference or not, is just an example to make you understand that you can reverse an array using .clone() in one single step.

The point is the clone/copy array functionality, that's why those redundant methods exists. To avoid modify the original one.

@theScottyJam is just an example, of course I had to write some native functions to exemplify the method correctly. The idea is to have a fast way to copy an array and modify it directly.

Of course the transform methods need to be rewritten.

If you're telling me that the current implementation of .toSorted(), .toSpliced() and .toReversed() IS NOT different of .sort(), .splice() and .reversed() then we have a HUGE, HUGE problem, that means that those methods are WORSE than I tought. Because that means that those methods are just copying and modifying the referenced array. 2 steps!!!!

That could explain why, at least on Deno, the benchmark of copy an array by myself and then reverse it vs .toReversed() was exactly the same.

I'm afraid this doesn't work for a sort function - or for any function where the input of each member of the array is dependent on the entirety of the array. Since the transformer is called once per element, that means that you're forcing a minimum of O(N²) complexity on the whole operation.

What you've created is nearly the same as Array.prototype.forEach, but with a more complicated API and a larger memory footprint. (There's a reason the builtin functions like forEach use positional parameters for calling the provided function, rather than an object with named properties - in your implementation, the engine would have to allocate memory for a new object with the properties item, index, array, original, and args for each member of the input array.)

Remember, the goal of a JS programmer is to provide the engine with the information it needs to make the most efficient optimized machine-code implementation of the algorithm described by the JS code. The named method toSorted, for example, provides the engine with the information "I'm trying to create a sorted version of this array within newly-allocated memory space", a task for which there are any number of well-known, thoroughly-researched algorithms available. This clone method, on the other hand, only provides the engine with the information "I'm trying to create a new array based on this other array". The engine doesn't even know how many elements will be in the new array, so it can't preallocate the right amount of memory space, which will inevitably lead to worse performance.

In other words, clarity might be a better goal to aim for than flexibility/power, when they conflict.

2 Likes

I'm not trying to implement the .clone() method, is just an example! For any reason everyone here is taking everything literal even when I told that is an example.

Of course the real .clone() implementation will be different of it, better thought.

Also, if we're ignoring all the points and just be focusing on the performance. We can say the .clone() method will be a very well improvement for custom "self-mutable" functions, a much more for large arrays.

Also, about the information passed to the Js Engine. As I explained before, you can use arguments to let the engine know which transformation we want.

You could pass Array.prototype.reverse as argument and internally use the .toReversed() implementation without problems.

The .clone() is so much clear than .toSorted(). So many developers could be confusing that method with sort and harming their apps performance. The same for the rest

And one last thing: I don't know how much we could talk about performance when those methods has basically the same performance than the old methods to clone arrays...

Then if those methods does not improve the performance and are not descriptive... why do we have them?

Perflink | reverse vs toReversed Benchmark

Perflink | splice vs toSpliced Benchmark

Perflink | sort vs toSorted Benchmark

Or you could just call different methods for different transformations, which is a much better software engineering practice.
Having a single catch-all clone method either is too limited (like the example API you've given not being able to express a sorting transformation) or so generic that it becomes useless and slow.

Really I disagree with all of these points. They are not redundant, they do different things. Their names follow a clear pattern, the prefix "to" describes that they transform they array into something different. The learning curve does not double, you can either ignore those extra methods or you can apply your understanding of the existing mutable APIs to predict what the immutable versions do. This has nothing to do with DRY, they don't make your code more repetitive. They are limited in their application, yes - they follow the principle of doing only one thing and doing it well - but they do not limit you in writing your own code.

Your main gripe appears to be with

to which we answer "as many as are useful". There is no upper limit. Those 3 methods definitely are useful to many people.

You are expressing your own personal opinion about the .clone() method and the related methods.

Nice to know that you like them, thas fine and you can keep using it. I who cares about the Js Ecosystem will continue raising the problem of those methods.

As i did answer you before, that's just an example an can be modified to easily handle sort algorithms. We are developers, we can make things to happen.

You dissagree with my points. Ok. But you have 3 new methods that does the same as the old methods, that makes them redundant.

The to prefix does not explain what is happening to the array so much more than the original names does (sort, reverse and splice). The unique difference between the old and new methods are that those last ones makes a copy of the array to avoid mutate the original one. Where the .toSorted() name explains that? It does not.

The learning curve for those methods does not duplicate? The array methods are the more important part of the JS ecosystem. Not understanding those new methods may imply HUGE and hard to triagle bugs.

Those methods does not makes your code more repetitive, those new methods are repetitive by themselves.

Right now are just harmful for JS/TS.