A Tale of map and parseInt in Three Acts, or WAT You Will

Act 1: What?!

import { describe, it, expect } from '@jest/globals';
describe("map parseInt over an array of strings", () => {
it("should parse both numbers, but doesn't seem to", () => {
expect(["0", "0"].map(parseInt)).toStrictEqual([0, 0]);
});
});

The actual result is [0, NaN]. Huh.

Act 2: Hmmm…

import { describe, it, expect } from '@jest/globals';
describe("map parseInt over an array of strings", () => {
it("should parse both numbers, but doesn't seem to", () => {
expect(["0", "0"].map(s => parseInt(s))).toStrictEqual([0, 0]);
expect(["0", "0"].map(parseInt)).toStrictEqual([0, NaN]);
});
});

This test passes. Wait… when does eta-conversion fail?!

When there is more than one argument to the function we are “mapping”! What does Array.map() actually do?

callback is called with three arguments: the value of the element, the index of the element, and the object being traversed.

--- https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.map

Act 3: Aha!

import { describe, it, expect } from '@jest/globals';
describe("map parseInt over an array of strings", () => {
it("should parse both numbers, but doesn't seem to", () => {
expect(["0", "0"].map(s => parseInt(s))).toStrictEqual([0, 0]);
expect(["0", "0"].map(parseInt)).toStrictEqual([0, NaN]);
expect(parseInt("0", 0, ["0", "0"])).toStrictEqual(0);
expect(parseInt("0", 1, ["0", "0"])).toStrictEqual(NaN); // because radix != 0 and radix < 2
});
});

Since parseInt() interprets the index as a radix, and the radix is not 0 (where it would default to 10) and not 2 (which would result in interpreting the text as a binary number), the result is NaN. Obviously. (Read step 8.)

And that’s how we learn that eta-conversion with Array.map() in TypeScript is special.

Epilogue

import { describe, it, expect } from '@jest/globals';
describe("map parseInt over an array of strings", () => {
it("should parse both numbers, but doesn't seem to", () => {
// Explicit parameter passing works as expected
expect(["0", "0"].map(s => parseInt(s))).toStrictEqual([0, 0]);
// eta-conversion fails, because map() passes 3 arguments to the function, not only 1
expect(["0", "0"].map(parseInt)).toStrictEqual([0, NaN]);
// Notably, map() invokes callback(element, index, sourceObject),
// but JavaScript/TypeScript can ignore superfluous arguments, which means...
expect(parseInt("0", 0, ["0", "0"])).toStrictEqual(0);
expect(parseInt("0", 1, ["0", "0"])).toStrictEqual(NaN); // because radix != 0 and radix < 2
});
});