Hacker News new | ask | show | jobs
by vortico 1086 days ago
The reason is surprising but perfectly valid once you remember the spec of map() and parseInt(). Love JS WTFs.
3 comments

Yep. I actually recall someone on the team running into a similar issue during the workday.
I wouldn't even call it a JS WTF. If you use a tool wrong, why be surprised about wrong results?
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.

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.

> 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.

TypeScript has the same problem.
It helps avoiding number of arguments problems, though. Of course it doesn't change the core semantics of JS.
> 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.

If everybody uses a tool wrong, the problem isn't with everybody, it's with the tool.
It's unexpected.

No other language does this.

> 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.

To 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.

Oh, I definitely could post an equivalent example in Python and C. There's simply not enough space on the sidebar to do it ;)

If you ever, like at all, had the pleasure of using any Python library without type hints - past iterations of numpy and in particular matplotlib come to mind - you'd be blown away by the amount of

  def some_function(*args, **kwargs)
Have fun trying to figure those out even with an API reference at hand. Fun times.

Also C does have equivalent syntax, namely variadic functions in combination with function pointer arguments. You can design all kinds of crazy interfaces with that, too, which will exhibit strange and unexpected behaviours if used incorrectly. Heck, name a single C noob who didn't cause a segfault while trying to read a number using sscanf().

When it comes to stdlib interface design, C is actually much more offensive than JS :) strtok() comes to mind. More footguns than stars in the sky just in that innocent seeming function alone. And don't even get me started with the C++ STL...

So no, it's not just JS that makes design choices that seem odd and unintuitive - you'll find them in every language that is actually in widespread use and "battle tested". It's funny to me how some people try to single out JS in that regard, even though it's in no way special when it comes to this.

Python 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'
Now 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.

JS 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.
Overloading on arity isn’t that uncommon in popular languages like Java
The combination with the design of common-across-languages iterator methods like map, filter, reduce is uncommon. (Neither is doing it implicitly for all functions.)
It's not unique at all: C has this feature as well - just look at printf() and other functions that accept variable argument lists.
That'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

> You cannot pass in an arbitrary number of arguments to other functions.

More specifically, you could in C89 but it was removed because it's a bad feature.

https://godbolt.org/z/j4v4GT3Wd

In 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.
How on Earth is that a WTF? Ignoring the specs will lead to unexpected results regardless of the language or API used.