5 ms·
This isn't strange or surprising. parseInt takes two arguments, the second one is the radix and map will call with three arguments, the value, the index and the
by roryrjb 7y ago
This isn't strange or surprising. parseInt takes two arguments, the second one is the radix and map will call with three arguments, the value, the index and the whole array. You just have to know this and it might be different in other languages. [ ... ].map(x => ...) is the right way to do this.
- crossman 7y agoYeah.. Didn't even need to load the article. You're passing the index as the radix. Makes sense
- mpolichette 7y agoYeah, I’m not surprised at all by this. I get that there are a lot of weird things in JS but I don’t think this is a JS oddity. Surprise! You need to know the basics of how your language works.
- arcticbull 7y agoThat's apologist talk pure and simple. The language, silently, does something that's almost certainly wrong. The language has enough information to provide you with a helpful warning or error message that you probably don't want to do this, but instead, it violates the principle of least surprise by just doing the wrong thing instead. The correct error is something along the lines of: let numbers = input.iter().map(parseInt).collect::<Vec<_>>(); --> src/main.rs:10:32 | 4 | fn parseInt<T>(input: T, radix: u32) -> Option<i32> where T: AsRef<str> { | ----------------------------------------------------------------------- takes 2 arguments ... 10 | let numbers = input.iter().map(parseInt).collect::<Vec<_>>(); | ^^^ expected function that takes 1 argument This isn't rocket science, it's basic language design. From time immemorial engineers have been making mistakes as they write software, so compilers evolved not to pretend otherwise and hope for the best, but to help engineers catch them. Except for one.
- pennaMan 7y agoThe map function fully expects a function with two arguments, as per documentation. Why would passing a function that takes two arguments to map be an error?
- benschulz 7y agoThe example given is taken from Rust. Rust does not allow overloading (even in terms of optional parameters, I believe). Well, Rust allows overloading by implementing different traits but then forces you to disambiguate where necessary.
- estebank 7y agoThe map function has a bad API surface that interacts poorly with the way people expect it to work. In a better designed API, if you needed the index of each yielded value other languages have some kind of enumerate method that will allow people opt-in to getting the index. Then if you needed the replicate the presented situation you would have something like ['1', '7', '11'].enumerate().map(([val, idx]) => parseInt(val, idx)) I understand that this pattern of having extra "helper" arguments is pervasive in JS and might seem less exotic to the community, but I feel it is a problematic approach, as it is foot-gun prone.
- deleted 7y ago[deleted]
- nwellnhof 7y ago> The language has enough information No. > expected function that takes 1 argument `map` expects a function that takes one, two, or three arguments.
- arcticbull 7y agoWell there's your first problem haha.
- nerdponx 7y agoIMO the real oddity here is that Javascript works like this.
- jlarocco 7y agoSo it's clickbait?
- syntheticcdo 7y agoThe article is not clickbait but the takeaway is easily misinterpreted. Instead of "JavaScript is such a weird and confusing language" it should be "a lot of people are using JavaScript without understanding the method signatures of commonly using methods".
- happytoexplain 7y agoThose sound like two sides of the same coin.
- tntn 7y agoa lot of people are using JavaScript without understanding the method signatures of commonly using methods because JavaScript is such a weird and confusing language that has nonstandard method signatures for standard functions.
- fgonzag 7y agoA lot of people are using JavaScript expecting it to conform to the principle of least surprise. Map applies a function f to each element and returns the result. If you want to supply a second argument, you either have to partially apply the function or manually supply the arguments. JavaScript's map is different from what the rest of the world calls map, and that's the problem. I would never have guessed that it was passing the index as a second parameter, I would have expected a compiler error.
- matharmin 7y agoThe surprising bit comes in with the fact that: 1. parseInt is used with only a single argument in most cases. 2. Only the first argument of map is used in most cases. Using multiple parameters for those are so uncommon that it's easy not to realise that it's supported. _If you know all of that_ it may not be surprising to you, but in most cases there is no need for the average developer to know it, making it very surprising when it crops up like this.
- acdha 7y agoThere's some interesting history for awareness of the first issue: parseInt used to be a notorious source of bugs and so it used to be more common to see it with a second argument on any project which used a linter in that era. ES5 deprecated that behaviour and it's been widespread enough that code which started in the IE9+ era now looks like the very old buggy code omitting the radix, so anyone getting started relatively recently quite reasonably may never have needed to learn about it: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt#ECMAScript_5_removes_octal_interpretation https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe... https://eslint.org/docs/rules/radix https://eslint.org/docs/rules/radix
- sdegutis 7y agoI didn't know about the second parameter until I saw it in a TypeScript signature a few months ago, which acted as a sort of warning to me to avoid using it incorrectly. But TypeScript doesn't catch this specific bug because it's technically correct[1] from an API stand-point, as the second parameter to parseInt is a number and the second argument to map's callback is a number. For as many situations as TypeScript genuinely helps you, there are just as many situations where it gives you false security, like in this case, and I'm starting to consider not using it anymore. [2] [1] not actually "the best kind of correct" despite popular television quotes [2] https://sdegutis.com/2019-06-20-considering-removing-typescript https://sdegutis.com/2019-06-20-considering-removing-typescr...
- carlmr 7y ago
- nkrisc 7y agoI'd certainly call it unintuitive if you run into it yourself and aren't aware of this, but once you unpack it we've seen it's expected behavior.
- sorokod 7y agoThis can be said about pretty much any bug.
- happytoexplain 7y agoI've developed applications in JS, but don't have extensive experience with it, and I couldn't spot the problem. Frankly, I think if `['1', '2'].map(parseInt)` is both valid and does not result in a collection containing the integers 1 and 2 in that order, that indicates a catastrophic failure of design at some level. These things happen, but usually you have to dig down to a less bog standard example to find it. The "you just have to know" argument is often unavoidably valid, but in this case I think it's way off the mark.
- lucideer 7y agoThe problem is that this article is attributing this problem to JS, whereas the problem is with either computer science or human decimal bias: i.e. the radix is not a concept invented by JS. If you think it should be ok for all devs to believe that parseInt === parseDecimalInt then maybe all languages should be decimal-only. That isn't the case though.
- sixstringtheory 7y agoAll languages I know of _default_ to a radix of 10 though, allowing other radixes where a prefix is added like 0b111010, 0xa7b45fd9, etc. Seems intuitive that if parseInt allows omitting the radix parameter, that it should default to whatever radix a standard integer primitive would default to.
- ianjsikes 7y ago> Seems intuitive that if parseInt allows omitting the radix parameter, that it should default to whatever radix a standard integer primitive would default to. But in this case, the radix is _not_ omitted. The map function passes the index of the current iteration as the second parameter to the function it is passed. It is kinda like this: ``` ['1','7','11'].map((item, index) => parseInt(item, index))
- sixstringtheory 7y agoThanks for connecting the dots for me, I see it now!
- ballenf 7y agoWhat's wrong with just: [...].map(Number) ?
- markdoubleyou 7y agoYeah, this gotcha doesn't bother me nearly as much as the classic: [9, 10, 11].sort() [ 10, 11, 9 ]
- sametmax 7y agoIt may be logical but: - I know all that, have been programming for 15 years, yet I still would have done the mistake. - Simple unit tests may very well not catch this bug the first time. - The language design allows your brain to ignore the index parameter because JS accepts superfluous parameters, which is a terrible decision. - map() is a mapping primitive. It's supposed to adapt a type to another type so that you can pass it to a monoid. JS breaks this convention. - Even traditionally not functional languages know better than that and separate iteration from indexing. E.G: python map() just maps, and if you want numbering, you use explicitly enumerate(). Ruby has each() and each_with_index(), etc. Just another of the numerous sucky things in JS. They are not big, but they accumulate very quickly and make it one of the worse language existing. Certainly the worst of all modern stack languages. It's a shame it has a monopoly on the most awesome platform in the world: the web. In fact, it's such a big problem the most popular JS projects are all things to avoid coding in JS or workaround JS deficiencies: typescript, coffeescript, jsx, babel, webpack, lowdash ...
- megous 7y ago> - The language design allows your brain to ignore the index parameter because JS accepts superfluous parameters, which is a terrible decision. Without that we would not be able to have variable length argument lists in the past.
- sametmax 7y agoYes, but again that's the consequence of another bad design: not including spread or another metaprogramming system for this. And not having default values either. Spread has existed in Python forever under the name of "splat operator", default values as well. Same with ruby. That what I meant when I said "they accumulate". A bad design decision not only affect the user cognitive load and productivity, but it also cascades to the rest of the language and shapes it.
- o_nate 7y agoWhenever I learn about another of these Javascript foot-guns, I come away with a greater sense of awe at the relentless good sense that went into the design of Python. Not sure if it's a kind of super-human Dutch ability to foresee ramifications of design decisions, or if it's just that Python developed organically over a period of time with healthy feedback from smart and invested users.
- otakucode 7y agoMap ever calling anything with 3 arguments is absolutely strange and surprising and should be borderline offensive to most anyone familiar with the operation from ANY other language.
- wutbrodo 7y ago> This isn't strange or surprising. > You just have to know this Hidden, silent, unintuitive behavior that you "just have to know" (and remember each time you might read or write the code) is absolutely strange and surprising in an engineering context. Javascript's deadly combination of unnecessary unintuitiveness (map's ludicrous API) and permissibility (ignoring extra arguments) strikes again. The permissibility of guessing what malformed code means is actually somewhat defensible in Javascript's case, since the incentive ecosystem of the Web is complex and frontend parsing has long had a tradition of best-effort parsing instead of throwing an exception. But the absurd signature of map here is half the problem, and there's really no good explanation for that than yet another instance of "Javascript is a dumpster fire that should only be used when forced to". Similarly "easy-to-use" languages like Python can afford strong typing and rejecting extra args when not specified because they're not bound by the Web's permissibility. But Python also, for all its flaws, has adults making language decisions and does the sane thing with APIs like map without loss of expressiveness. Adding loop metadata like indices requires an explicit call to enumerate(), trading off an iota of verbosity for intuitiveness, which is one of the most important things in writing code that can remain productive and bug-free.