ramda / ramda

:ram: Practical functional Javascript

Home Page:https://ramdajs.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

clone new Array(number);

aboveyunhai opened this issue · comments

I realized that ramda will remove all value from the new Array initialization with predefine size, is this intentional?

const data = new Array(5);

console.log(data); // [undefined, undefined, undefined, undefined, undefined] , length: 5
console.log(Ramda.clone(data));
console.log(result); // [] length: 0
 

as oppose to Lodash

console.log(data); // [undefined, undefined, undefined, undefined, undefined] , length: 5
console.log(Lodash.clone(data));
console.log(result); // [undefined, undefined, undefined, undefined, undefined] , length: 5

Welcome to the wonderful world of javascript!

Kidding aside. The behavior of new Array(x) is very often misunderstood. new Array(5) effectively creates { length: 5 } with prototypeOf Array. This is different from new Array(5).fill(undefined) that would be { 0: undefined, 1: undefined, 2: undefined, 3: undefined, 4: undefined, 5: undefined, length: 5 }

console.log(new Array(5).fill(undefined)) // [undefined, undefined, undefined, undefined, undefined] , length: 5

The way that Ramda clones arrays is by starting with an empty array and cloning each ownProperties's value from the original value over to it. The problem with this implementation is that for new Array(5), there are no ownProperties (The length property is non-enumerable).

This means that you put in an Array of length 5, and get out an Array of length 0. However, both the input and output array are still technically empty.

In general, we get a lot of variety on if [] === new Array(5) with respect to how length: 5 is treated when there are no keys

const data = new Array(5);

R.equals([], data); // true
lodash.isEqual([], data)); // false

data[0]; // undefined for `0` or any index
[...data]; // [undefined, undefined, undefined, undefined, undefined] , length: 5
Array.from(data); // [undefined, undefined, undefined, undefined, undefined] , length: 5

Obect.keys(data); // []
data.forEach(v => console.log(v)); // noop
for (let i; i <= data.length; i++) console.log(data[i]); // `undefined` x5
for (const key in data) console.log(key); // noop
for (const value of data) console.log(value); // // `undefined` x5

console.log(structuredClone(new Array(5))); // [] , length: 5

The question is: do we respect the value of the length property over the actual existance of the array?

My vote would be to do what structuredClone does here. because that would mean for all the operations shown above for a new Array(5), would give the exact same behavior using if used again with clone(new Array(5)) (not the current behavior, be aware, we'd change it to that)

This would simply mean, instead of starting the clone with an empty array [], we'd start it with new Array(originalArray.length)

@Harris-Miller wow thx, didn't expect such a comment with good educational materials.
Just an unrelated question based on your explanation, if that's how our ugly JavaScript :p Array(size) initializes the array with a given length without empty value, will it still have any performance gain?
Because in other languages generally if you pre-define size of an array, it will usually have some performance gain comparing to dynamic size since the address is continuous.

I wonder does javascript still handle it internally and correctly like other langs but with that quirky behavior, or it's just the same as dynamic array new array()?

I wonder does javascript still handle it internally and correctly like other langs but with that quirky behavior, or it's just the same as dynamic array new array()?

According to this stack overflow post, it does matter

@CrossEye @kedashoe @adispring This is a worth-file micro-optimization for any point within ramda where we are creating a new array where the length is known ahead of time, clone, map, etc...

Looks like _map is already doing this, we could do it in _clone as well:

// _clone.js
// is
case 'Array':   return copy([]);
// could be
case 'Array':   return copy(Array(value.length));

I'm sure there are other locations as well