15 ms·
Stage 3 Proposal: Array.prototype.at
- beaconstudios 5y agoconst at = (arr, i) => i >= 0 ? arr[i] : arr[arr.length + i]; Why does the core language need to be extended to incorporate this?
- cphoover 5y ago+1
- zodiakzz 5y agoJust Google "left-pad" to know why. I think .at() also doesn't have to look up the entire prototype chain so it has performance benefits as well.
- beaconstudios 5y agoconst leftpad = (str, len, char = ' ') => str.length >= len ? str : (char.repeat(len - str.length) + str); Anybody who installs a dependency instead of writing a one line function is just leaving themselves exposed for no real benefit. Providing the at function outside of the prototype chain like I did above will not incur the cost you mentioned, even in the unlikely case that such calls are the bottleneck of your application. Plus, if array lookup calls are your bottleneck then you probably need a different data structure.
- zodiakzz 5y agoReinventing the wheel is error prone. Noone wants to bloat their code/time with unit tests for all these utility methods. Just look at your at() function, I already spotted a bug, if a numeric string is passed: `([].length + '-1') === '0-1'`
- beaconstudios 5y agoyes that is obvious - my point was that the functionality is minimal. with guards: const at = (arr, i) => { if(!(Array.isArray(arr) && typeof i === 'number')) { throw new Error('invalid arguments'); } // same as before }; you wouldn't just commit a single-line function in your code. But there are 10,000 micro-functions that could be in the core of JS. Why bother to add this, especially if it's so easy to implement? There are surely better things the JS committee could be putting their time towards, like providing functionality that is presently lacking in JS (maybe they could finally get around to advancing the pipe operator through the TC stages).
- Osiris 5y agoYour number guard is insufficient. NaN, infinity, 2.23 are all Number but not supported values for the function. This goes to show that it's NOT trivial to implement in a robust way.
- beaconstudios 5y agoIt is trivial. It requires a few more lines than my example, but my example was intended to show the core use case. All I'm getting in replies is nitpicking about my example case... Can't you just assume when reading that I know how to write some simple guard statements? It's hardly rocket science.
- claytongulick 5y agoBecause it is such a common case? To invert your argument, why would you want millions of developers to have to waste their time writing this sort of thing for such a common need? Yes, it's clear that you could write the function correctly. Could everyone? What about bugs that aren't obvious? The runtime errors that result? Why would we want to waste so much developer time and effort globally, when we can just add .at() to the language?
- lhorie 5y ago
- Aachen 5y agoI agree that dependency hell is a problem, but I don't agree that reinventing the wheel is better. Including things a lot of developers will need in the language is probably the best way to approach things like left_pad, at least given these three options (reinvent, third-party, language inclusion).
- lucideer 5y agoYou're assuming this is a decision available to each app developer. In fact these dependencies are usually chosen by the developer of a library that's a dependency of a dependency of a dependency of a dependency of a library you choose as an app developer. Auditing the entire dependency tree of every library you choose is extremely arduous without proper tooling, and the tooling here has never been good (and even the more recently available better tooling is CVE-focused so won't highlight tells like package size/maintenance status/whatever other heuristics you might devise as a proxy for quality).
- easrng 5y agoor const leftpad = (str, len, char = ' ') => str.padStart(len, char);
- beaconstudios 5y agoHaha yes that would be the modern option.
- Cthulhu_ 5y agoBecause at some point someone will - and probably has - make a library out of this, adding yet one more to the tens of thousands of files that my cruddy server can't handle already because of the sheer quantity. Plus you're probably missing a few dozen edge cases and optimizations that the native version would incorporate. We wouldn't have been in node_modules dependency hell if they incorporated a few more things into the standard library or language.
- beaconstudios 5y agoin my experience, the vast majority of node modules I have installed don't provide these tiny little functions, but are: - frameworks like react or express and extensions for the same - meaningful libraries like axios, ramda, date-fns or qs - transpiler/bundler enhancements like webpack loaders or babel presets I would prefer that the JS committee focused on bringing meaningful new capabilities to node and the browser that are currently lacking, like they did with the various HTML5 libraries, fetch(), ES classes and so on, rather than providing tiny functions that aren't even providing new functionality (array indexing is easy and idiomatic in JS). If there was no opportunity cost then sure, add tiny pointless builtin functions all day long.
- lucideer 5y agoYou seem to be garnering a lot of downvotes without replies so I'll bite: it sounds from your comment like you've never looked into the subdependency tree of those frameworks and meaningful libraries that you list (which seems like it would be hard to miss tbh for anyone that's ever opened up the node_modules directory). Those tiny little libs providing tiny little functions are likely not direct dependencies of your application, rather dependencies of a subdependency of a subdependency of a subdependency of your preferred "meaningful" library.
- beaconstudios 5y agoI'm aware of that. Unfortunately that's always going to be the case that some people who write libraries you depend on indirectly are not very good at programming and lean heavily on other dependencies to provide basic functionality. That will be the case regardless of how all-encompassing the stdlib is, and is entirely dependent on the culture of practice of the language you're using. JS is both beginner-friendly and the culture is focused on personal marketability, so you're going to get a lot of beginners land-rushing to put out basic libraries that don't do anything to cater to other beginners, in order to put the number of stars/npm downloads on their blog or CV. That's not going to change with the number of new core features.
- jahewson 5y agoArray parameter should be last, surely? Or why not extend the prototype? If only we had some way to standardise such decisions...
- johnfn 5y agoSubtle readability improvements on patterns that see extensive use actually make a difference. I had the exact same thoughts when `.includes()` was proposed to be added to the spec - it's just `.indexOf() !== -1`! - but time has proven me wrong, and I suspect it will prove us wrong about `at` as well. I've written an `.indexOf() !== -1` check hundreds if not thousands of times, and I've gotten the conditional wrong enough times to actually need to revert a change in prod. I have similar thoughts about .at(). Taking an element from the end of the array is ever-so-slightly error prone. Sure, you won't make a mistake this time, or the next time, but write it a hundred times, or a thousand, and I bet a bug will slip in. It's darn near impossible to get `.at(-1)` wrong, however.
- beaconstudios 5y agoThat's actually a pretty reasonable argument, the 'length - N' form is actually quite prone to off-by-one errors so .at might help there.
- NicolaiS 5y agoIndeed, and as others have stated it is really hard to allow `[-1]` without breaking old code. Also: `[1, NaN, "hi"].indexOf(NaN)` is -1 but `.includes(NaN)` returns true as expected.
- nawgz 5y ago> `[1, NaN, "hi"].indexOf(NaN)` is -1 but `.includes(NaN)` returns true Brutal.
- alerighi 5y agoExcept you would never get to use this method, because it will take years for all browser vendors to implement it, and even then, you still have a number of users that use for a reason or another Internet Explorer. You need to use a transpiler (like Typescript), but at this point, there no reason to implement that in the language, a transpiler can implement the feature by simply adding the method to the prototype of the object, like this: Array.prototype.at = function (i) { return i < 0 ? this[this.length + i] : this[i] }
- ezekg 5y agoAm I reading this correctly — it's an alias for arr[i] and str[i]? I know languages like Ruby have arr.at(i), but why?
- twoodfin 5y agoA quick read suggests that it adds negative indexing from the end of the array.
- Pxtl 5y agoImho that violates the principle of least astonishment - that "at" would have such different behavior from "[]" for negative numbers.
- pininja 5y agoAccessing negative indexes currently returns undefined. This adds support for indexing from the end of an array with negative numbers.
- franciscop 5y agoThis works, but not the way you'd think: const arr = []; arr[-1] = 'Hello world'; console.log(arr[-1]); // Hello world Instead of using the negative index, it stringifies the "-1" and uses it as the key of an object both when writing and reading. Thus, arr.length still remains 0
- Phenix88be 5y agoSo instead of fixing the language and allow to use array[-1], they add an other way to access array element. This is why I don't like JavaScript, instead of fixing feature, they add new feature to fix previous feature.
- Spivak 5y agoThe problem with JS language development is the mountain of code that it would break if you do anything except append features. array[-1] = “foo” This already works in JS but doesn’t do what you expect and just assigns the property.
- cogman10 5y agoJavascript array indexing is a MESS. This array["foo"] = "bar" is valid javascript.
- Osiris 5y agoWhy does this make it a mess? Array already has a bunch of properties that aren't elements in the Array that come from the prototype, like join and slice. Your example is just adding a custom property to the array.
- cogman10 5y agoBecause in most other languages, an array is an enumeration of values. That's it. In Javascript, arrays are actually dictionaries. But not full dictionaries, rather just dictionaries that can have either a string or numeric key. It's messy because an array in javascript isn't just "an array" it's this mismesh of features that are unexpected to a new-to-javascript developer. Unexpected is the enemy of readable code.
- gsnedders 5y agoNo, arrays are just objects with a magic "length" property (technically: a special [[DefineOwnProperty]] internal method which sometimes also mutates "length"). Like objects, they only support strings as keys. c.f. https://tc39.es/ecma262/#sec-array-exotic-objects https://tc39.es/ecma262/#sec-array-exotic-objects
- trinovantes 5y agoThis is going to confuse C++ developers where vector::at() explicitly throws exceptions on out of bounds access But I guess the intersect between C++/JS developers is too small to care
- yoz-y 5y agoAs one of those c++/js developers I'd just say that I do not map concepts from one language to another because it rarely works anyways.
- IshKebab 5y agoI agree. I would expect it to throw an exception. However I think there is a better way to support that - make `throw` an expression rather than a statement. Then you can do a[10] ?? throw new Error() which currently doesn't work.
- sfinx 5y agoIf I understand this correctly it is to add negative index support to arrays. How will findIndex work when this is introduced? It currently uses -1 as a return value when no element satisfies the testing function.
- jakelazaroff 5y agoYou can’t have a negative array index. Passing a negative number will just make this function count backwards instead of forwards. [1, 2, 3, 4].at(0) === 1 [1, 2, 3, 4].at(1) === 2 [1, 2, 3, 4].at(-1) === 4 findIndex’s behavior is unchanged.
- almost 5y agoIt’s not adding negative indexes, a negative number will index from the end. So [0, 1, 2, 3].at(-2) === 2
- onion2k 5y agoThat would mean getting the last element of the array would be [0,1,2,3].at(-1). I don't like that very much. Mind you, I don't like the idea of using [0,1,2,3].at(-0) either...
- jakelazaroff 5y agoDon’t think of it as counting from the end of the array — think of it as counting in reverse from the 0th element. .at(1) gets the next element (index 1) while .at(-1) loops around and gets the “previous” element (index 3, in this case). Or, if it’s more intuitive, you can think of the array index as an unsigned integer where the max is equal to the length of the array. If you try to assign -1 to a uint8, the result will be 255 — the highest possible value (the last index).
- zodiakzz 5y agoGood thing we don't have to stagnate progress just because what we have will never be perfect.
- 5y ago
- pedrow 5y agoIn the proposal text, what's the meaning of the '?' and '!' - for example in "Let O be ? ToObject(this value)"
- IainIreland 5y agoRoughly speaking, '?' means that the operation might throw an exception, which should be propagated if thrown, and '!' means that the operation should never fail. (In Rust terms, '? ToObject(this value)" -> "ToObject(thisValue)?", and "! ToObject(this value)" -> "ToObject(thisValue).unwrap()".) See: https://tc39.es/ecma262/multipage/notational-conventions.html#sec-returnifabrupt-shorthands https://tc39.es/ecma262/multipage/notational-conventions.htm...
- dlojudice 5y agoJS urgently needs support for decimals [1] and thus be able to enter sectors such as scientific and financial. V8 + dynamic language + better math support = perfect environment for many applications out there [1] https://github.com/tc39/proposal-decimal https://github.com/tc39/proposal-decimal
- cogman10 5y agoCouldn't that be done with WASM today? It looks like a few people have managed to build GMP targeting WASM. You might miss out on some SIMD operations, but you'd be able to do 90% of what you'd want with scientific calcs. (admittedly, it'd be nice if this were part of the language).
- diamond_hands 5y agoThere's still a ton of overhead for WASM and it's not first class. Using WASM is a lot like using C code in python. It's still nice to have features as first class citizens :)
- schwartzworld 5y agoIsn't most python just stringing together c and Fortran libraries that expose a python interface?
- Macha 5y agoIt amazes me at Python web engineers and Python ML engineers who go along completely unaware of the existence of the other group in any large numbers. (I say this as someone who went to my local pycon 5-6 years ago and was surprised that ML talks were like 30% of the topics)
- runarberg 5y agoHonest (possibly stupid) question: Do people working on statistical and probability application never need a numeric type optimized for probability values (i.e. values between 0 and 1 that can get really close to either; for example the really almost certain value of p = 1 - 10^-13)? In the same way that financial application needs a decimal type? I’ve only worked on probability applications at a surface level for a tiny bit and never really needed it, but I kept wondering whether there were such a numeric types, and if not, what people did if they needed it.
- deleted 5y ago[deleted]
- 11235813213455 5y agoThis will be handy for a multitude of string libs, for example string diffing (fast myers diff, ...)
- 11235813213455 5y agoOh actually I thought '\u{1f4a9}'.at(0) === '\u{1f4a9}' but np, after reading the docs, it's just like charAt except for negative indices Quite disappointed so, my parent comment doesn't stand too
- cpeterso 5y agoThe MDN documentation for Array.prototype.at is easier to understand than the linked proposal: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/at https://developer.mozilla.org/docs/Web/JavaScript/Reference/...
- z3t4 5y agoIm a JS veteran, and not a single word in the article made any sense to me.