Fork me on GitHub
#off-topic
<
2023-03-17
>
dgb2316:03:00

I don't understand what's written here. But I have a deep appreciation for being able to rely on such a mature and optimized runtime.

dgb2316:03:03

It's insane how much work and thought they are putting into this.

Ben Sless17:03:41

Proof that you CAN put enough lipstick on a pig

😅 2
seancorfield19:03:40

Nice! Some of my PhD work back in the '80s was on GC implementation so I've continued to be somewhat fascinated by this whole topic forty years on...

👀 2
mauricio.szabo15:03:26

Somebody versed in JS can explain to me what Array(100) actually do?

thomas15:03:27

I think it creates an Array with length 100 and it is empty I think

☝️ 4
thomas15:03:48

but please do check, I'm not a JS expert either

hkjels15:03:35

let arr = new Array(100) arr.fill('foo')

hkjels15:03:45

should give you 100 foo's

mauricio.szabo17:03:10

But I can't actually iterate on this array with map or forEach, so what exactly is in this array?

hkjels17:03:57

It’s empty until you fill it. If you open the browser console and write dir(new Array(100)), you can investigate what’s there

mjw20:03:40

In nearly 15 years of writing JS on an almost daily basis, I have never once used the array constructor like this.

hkjels21:03:04

Give it another five and you will have used it once 👍:skin-tone-2:

😂 4
skylize02:03:32

Interestingly enough, you can make the empty array usable by filling it with "nothing" (which is more specifically actually undefined), because the fill function creates any missing indexes based on the length.

Array(5).fill().map(() => "foo")
// =>
['foo', 'foo', 'foo', 'foo', 'foo']
> so what exactly is in this array? It is an Object with Array.prototype as its prototype and a length property set to the value of your argument. Or from the mental model of treating an Array as somehow different from an Object, you could say it is a "sparse Array", which is so sparse as to be equivalent to empty. You can get a similar effect from Array-literal syntax with a bunch of commas, but no values, or by setting the length of an empty Array.
[,,,,,]
// =>
[empty × 5]

const xs = []
xs.length = 5
[empty × 5]
Indexed values on Arrays are no different from any other property. And in fact, the indexes are stored as strings, not numbers. The "magic" of an Array is in the Array prototype, where there is logic to handle treating integral numeric keys as a sequenced index. You can assign any arbitrary property to an Array. And if the key of said property happens to be integral, it will be accessible to Array methods such as Array.prototype.map.
const inc = x => x + 1

// Fine to just create an Object with the prototype
 // instead of calling the constructor. Though,
 // initially it will be missing a length.
const xs = Object.create(Array.prototype)

// Ready to act like an Array, but browser confused
 // by missing length prop, sees it only as Object.
xs
  // => 
  Array {}

// Once we add a length, the browser will recognize
 // it properly and update its print method.
xs.length = 0
xs
  // =>
  []

// Correction?: I'm thinking that ^ is probably
 // _not_ about the browser, but rather
 // Array.prototype.toString() abandoning its
 // custom logic if no length is found.

// Add arbitrary props.
xs.foo = "foo"
xs["bar"] = "bar"

// Add indexed props with number or string.
xs[0] = 0
xs["1"] = 1

// Numeric keys are stored as strings.
Object.keys(xs)
  // =>
  ['0', '1', 'foo', 'bar']

// Indexed props displayed first without keys,
 // then any other props with keys.
xs
  // =>
  [0, 1, foo: 'foo', bar: 'bar']

// Array.map ignores the non-integral keys, and creates
 // a brand new Array from the indexed values.
xs.map(inc)
  // =>
  [1, 2]


// Whoops! Push misbehaves because we forgot to
 // update the length. Overwrites value at 0 index
 // before updating length to 1.
xs.push(20)
xs
  // =>
  [20, 1: 1, foo: 'foo', bar: 'bar']

xs.length
  // =>
  1

// Mismatch between count of numeric keys and a
 // non-zero length causes interesting behavior:
 //  Any number outside the bounds of length is
 //  treated as not an index.
xs
  // =>
  [20, 1: 1, foo: 'foo', bar: 'bar']

xs.map(inc)
  // =>
  [11]

// We can fix that by fixing the length.
xs.length = 2
  // =>
  [11, 2]
> But I can't actually iterate on this array Technically you can, just isn't helpful. As demonstrated above, you iterate over existing integral keys.
[,,"foo"].map(x => x.toUpperCase())
  // =>
  [empty × 2, 'FOO']
So if there are no integral keys it's effectively a noop, because you are iterating over nothing. In the case of .map, the length property is copied to the new Array, so it also reports the same number of empty slots as the input.
[,,,].map(x => x.toUpperCase())
  // =>
  [empty x 3]

adi08:03:08

> Interestingly enough, you can make the empty array usable by filling it with "nothing" I, in fact, happened to use one of those fancy Array constructors in real life! Ref: the function frameInsideMask in this code: https://hanukkah.bluebird.sh/js/text_artist.js This is a contraction of the Array(<length>).fill().map(() => ...) idiom.

function frameInsideMask(ddwCharArray, xMaskWidth, yMaskHeight) {
	let maskingGrid = Array.from(
		{length: yMaskHeight}, (_, y) => Array.from(
			{length: xMaskWidth}, (_, x) => {
				return {"x": x, "y": y, "text": EMPTY_CHARACTER, "color": "", href: "" };
			}));

	for (const ddwChar of ddwCharArray) {
		let {x, y} = ddwChar;
    if (y < yMaskHeight && x < xMaskWidth) {
      maskingGrid[y][x] = ddwChar;
    }
	}

	return maskingGrid.flat();
}

mjw12:03:19

Hmm. My ad hoc range snippets in sandboxes/tests have been more verbose than they could have been: new Array(100).fill().map((_, i) => i)

adi17:03:16

Maybe only the work done is different?

Array(10).fill().map((_, i) => i) // three passes: initilise -> populate -> map
Because, this is longer, in fact:
Array.from({length: 10}, (_, i) => i) // single-pass?

yes 2
skylize17:03:28

>

Array.from({length: 10}, (_, i) => i) // single-pass?
> 
> ... Array.from(obj, mapFn, thisArg) has the same result as Array.from(obj).map(mapFn, thisArg), except that it does not create an intermediate array ... - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from() > So yes. That should be single pass. :the_horns:

skylize17:03:24

>

Array(10).fill().map((_, i) => i) // three passes: initilise -> populate -> map
> 
Only double pass. As I discussed in length above, initializing with the the arity-1 constructor does not create any indexes. It merely sets the value of the length property.

👍 4
skylize20:03:05

@U3Y18N0UC I expected to see some kind of response from you by now (or at least an emoji response on my post to acknowledge you got the info you were looking for). Were you able to follow my explanation? https://clojurians.slack.com/archives/C03RZGPG3/p1679107352829319?thread_ts=1679065526.533799&amp;cid=C03RZGPG3ng sparse Arrays in JS

hkjels20:03:47

Wow, this exploded. Smells like a blogpost is in the oven

skylize20:03:55

Did not mean to post full text of the whole previous post. Can't figure out how to remove it from the mobile app. Try to clean it up later

hkjels20:03:17

No, no. I think it’s great! 👍:skin-tone-2:

skylize20:03:46

I don't have any blog at the moment. So if there's a post cooking, it must be in your oven. 😊 Feel free to steal anything you need from what I wrote without guilt.

mauricio.szabo20:03:08

Yes, sorry. I want to test something before I answer, but the life happened and I could not test this...

2
mauricio.szabo13:03:12

Ok, yeah, on Javascript's own weirdness, it makes sense. I was just baffled that it's an array with length but without elements. It sounds like a Buddhist thing this: an array with 100 elements, but also with nothing = but now the "nothing" that Javascript uses, a "nothing" that's non-representable laughcry

mauricio.szabo13:03:27

Anyway, thanks for all the answers. I was under the impression that .map over this sparse array was returning me an empty array, but indeed, map does return another sparse array - it just doesn't iterate over the "empty" elements

2
skylize13:03:44

The key point that makes it make sense is to remember that everything in JavaScript inherits from Object. Array is no different. It's an Object with an Array prototype. Setting the length property does not reserve any memory, or any such thing. It's just a property, that gets used by methods from the Array prototype to decide how to behave.

mauricio.szabo14:03:53

The "key point" actually doesn't make too much sense for me. In Ruby, everything also inherits from Object, and arrays work how we expect (that meaning, length is a getter but not a setter, constructing new arrays make them traversable, etc).

skylize14:03:49

Well you must also realize that "inherits" means something different with prototypal inheritance. And length is neither a getter nor a setter. It's just a property. "Setting" the length is the mutable equivalent to (assoc m :length).

skylize14:03:14

It would seem reasonable for the constructor to initialize the indexes. But that also means you are automatically using memory for all those indexes. You can accomplish the same with fill. So it's no big loss that it doesn't, just a bit of "oh that's weird".