6 ms·
The reason is surprising but perfectly valid once you remember the spec of map() and parseInt(). Love JS WTFs.
by vortico 3y ago
The reason is surprising but perfectly valid once you remember the spec of map() and parseInt(). Love JS WTFs.
- bluepod4 3y agoYep. I actually recall someone on the team running into a similar issue during the workday.
- triyambakam 3y agoI wouldn't even call it a JS WTF. If you use a tool wrong, why be surprised about wrong results?
- paulddraper 3y agoIt's unexpected. No other language does this.
- dragonwriter 3y agoJS discarding extra positional arguments (and that being leveraged in map and similar methods) seems to be the unique bit. I don’t think any other popular language does that.
- qayxc 3y agoIt's not unique at all: C has this feature as well - just look at printf() and other functions that accept variable argument lists.
- Jtsummers 3y agoThat's a variadic function and those have to be explicitly marked as variadic. You cannot pass in an arbitrary number of arguments to other functions. https://en.cppreference.com/w/c/variadic https://en.cppreference.com/w/c/variadic
- qayxc 3y agoIn JS every function is a variadic function, though. The problem is that many people ignore that fact for whatever reason. function fn() { } is semantically the exact same as function fn(a, b, c) { } in JS.
- Jtsummers 3y agoYou're discussing this as if the critical "gotcha" was with `parseInt`. It's not the critical gotcha here. The critical one is actually with the semantics of `map` which behaves differently in JS compared to just about every other language with a map (or equivalent) which passes only the elements of the sequence (or sequences in some cases) to the function. See Python, Ruby, C++ (transform is the nearest direct match), Rust, Common Lisp, Scheme, etc. Using the example with `parseInt` just demonstrates this strange, and unique, design decision.
- qayxc 3y agoYou're looking at it myopically as well. JS has this semantic and the interfaces are designed with that in mind. Take C# for example. There is a List<T>.ForEach() function that passes the value to the delegate. Fine. But what if I require the index as well? I can't use List<T>.ForEach() in that case, because ignoring extra arguments is not part of C# function call semantics. The interface of map() has been designed to be as flexible as possible to cover use cases outside of just passing the current value. This matches perfectly with JS function call semantics that allow any number of arguments by default. Why doesn't parseInt() *always* require two parameters? Why is one design decision "strange and unique" (e.g. map() passing 3 arguments) while the other (parseInt() accepting one or two arguments) is perfectly fine? I also would be careful with citing the STL as being sound when it comes its interface design :) JS is different from other languages - big whoop. The same complaints about seemingly strange design decisions can be made for any sufficiently old and widespread language - JS isn't unique in this regard. There actually annoying strangeness in other things, e.g. confusing type conversions in places (e.g. ("" == false) === true; [1, 2, 3] + [4, 5, 6] === "1,2,34,5,6"), which is why "==" should never be used if you want to retain your sanity. The interfaces and standard functions, however, are as offensive or inoffensive as those in most other languages.
- fiddlerwoaroof 3y agoOverloading on arity isn’t that uncommon in popular languages like Java
- dragonwriter 3y agoThe combination with the design of common-across-languages iterator methods like map, filter, reduce is uncommon. (Neither is doing it implicitly for all functions.)
- qayxc 3y ago> No other language does this. Every language that supports default and variable arguments does this. This includes Python, C, and C++ and has absolutely zero to do with "loose typing" in this case.
- etra0 3y agoPython does not do this: > help(int) class int(object) | int([x]) -> integer | int(x, base=10) -> integer > list(map(int, ["1", "2", "3"])) [1, 2, 3] Even if you define a function that takes two parameters, it complains about not having a second one: >>> def to_int(a, b): ... return int(a, base=b) ... >>> list(map(to_int, ["1", "2", "3"])) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: to_int() missing 1 required positional argument: 'b'
- qayxc 3y agoNow replace def to_int(a, b): ... with the JS semantic equivalent of def to_int(*args): ... because that's how JS function declarations work. You simply have the option to assign names to positional parameters, i.e. function fn(a, b) { comnsole.log(a, b) } is just syntactic sugar for function fn() { const a = arguments[0] const b = arguments[1] console.log(a, b) } and that's what people seem to struggle with and argument over for whatever strange reason.
- paulddraper 3y agoTo be correct, all you have to do...all you have to do...is post a single example of the equivalent to the JS code that is producing the same result. But you can't!!! > Every language that supports default and variable arguments does this. Python: map(float, ["1", "2", "3"]) C: No equivalent syntax. C++ std::vector<std::string> a = {"1", "2", "3"}; std::vector<int> b; std::transform(a.begin(), a.end(), std::back_inserter(b), std::stod); # compile error You can say "well feature X exists elsewhere" or "library function Y exists elsewhere", but only JS makes the collective design choices that cause this phenomenon. Good, bad? IDK, that's subjective. But unique? Certainly.
- dragonwriter 3y agoIts a footgun that results from JS’s loose typing of functions, allowing them both to be called with discarded arguments and to be flexible in the number of arguments they accept. While each of those flexibilities can be useful, they interact in annoying ways. The fact that map passes three arguments but is often used as if it was passing one, and that parseInt accepts two arguments but is often used as if it accepted one makes it very easy to make this mistake.
- qayxc 3y ago> Its a footgun that results from JS’s loose typing of functions, allowing them both to be called with discarded arguments and to be flexible in the number of arguments they accept. This just goes to show how one can use a language without actually understanding its core semantics. Additional arguments aren't "discarded" at all: they're still available to the called function in via `arguments`, they're just not required to be assigned a name in the function signature. Basically, a Javascript function fn() declaration is equivalent to Python's def fn(*args): declaration and you have the option to assign a name to positional arguments. Any named positional argument that is not provided by the caller simply leaves the argument uninitialized, i.e. undefined. It's a core concept of the language and I'm always puzzled when non-beginners struggle with this. That's also a very good reason to not use vanilla Javascript at all and skip straight to TypeScript and its brethren instead.
- jjnoakes 3y agoTypeScript has the same problem.
- qayxc 3y agoIt helps avoiding number of arguments problems, though. Of course it doesn't change the core semantics of JS.
- jjnoakes 3y ago> It helps avoiding number of arguments problems, though This issue is all about the number of arguments problem, and TypeScript (in cases like this) won't flag that problem. Which is something I think it should. If you manually unroll the "map" and call parseInt() explicitly with the same arguments that map() calls parseInt() with, TypeScript will flag that. But not when map() is in the picture. I understand why they chose to do that, but I still disagree with it.
- vikingerik 3y agoIf everybody uses a tool wrong, the problem isn't with everybody, it's with the tool.
- qayxc 3y agoHow on Earth is that a WTF? Ignoring the specs will lead to unexpected results regardless of the language or API used.