A Tale of map and parseInt in Three Acts, or WAT You Will
A Tale of map and parseInt in Three Acts
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
});
});