(My top-level reaction to TFA, for background. I left it out because no-one asked.)

> Writing code for both computers and humans

Show me what you got.

> isNaN(defaultValue) ? NaN : defaultValue

Yuck.

> This made me pause for a moment.

Of course it did. I still pause every time I re-read it!

> This ternary is much better. This is expert-level programming. This is how to write code with empathy for other programmers.

No, it makes me pause every time I read it, and I cannot follow the path from "made me pause" to "expert-level programming".

Back to your comment:

>> dynamically typed so[...]

> It's not about dynamic typing in this case.

It is about dynamic typing. isNaN should not exist (except for specific cases such as machine floats where NaN means something concrete).

> If you saw that, your spidey-sense might start tingling, “This is a number—did they consider the NaN case?”

I want tingle-free programming. Let the compiler do the tingling for me. A Number-which-may-or-may-not-be-a-Number is not statically typed. I don't care if it's in a .ts file.

There are 15 more references to NaN in that module and they do not make for easy reading:

// Clamp to min and max, round to the nearest step, and round to specified number of digits
let clampedValue: number;
if (isNaN(step)) {
clampedValue = clamp(parsed.current, minValue, maxValue);
} else {
clampedValue = snapValueToStep(parsed.current, minValue, maxValue, step);
}

Why doesn't the above code match its comment? Why doesn't it look something like:

// Clamp to min and max, round to the nearest step, and round to specified number of digits
let output = input |> roundNearest(step)
|> clamp(min, max)
|> roundToDigits(numDigits)

Their code either calls into clamp() or snapValueToStep() based on whether 'step' is a number. Does that imply 'step' needs to be a number for the snapValueToStep case? If so, 'step' will need to be checked again inside that method. Why didn't they accept a typed number as input? Or - if they were forced to accept a string - check it at the top of the method? Oh wait - they did! Twice in two lines!

let clampStep = !isNaN(step) ? step : 1;
if (intlOptions.style === 'percent' && isNaN(step)) {
clampStep = 0.01;
}

So now 'step' and 'clampStep' are both in scope. They may or may not be numbers (insofar as the coder is willing to keep scrolling back up and re-reading code that at first "makes them pause", but then later makes them feel like it's "expert code".)

Why not:

const clampStep =
case parseNum strStep of
Just step -> step
Nothing
| intlOptions.style == Percent -> 0.01
| otherwise -> 1

* One definition site, so you can read all possible values from the leaves {step, 0.01, 1}

* The possibly-a-String step has been demoted to 'strStep :: String', so that 300 lines later, the coder cannot not accidentally pass 'step' to another function (and if he did, the compiler would stop it immediately).

* clampStep is actually a number, so no more NaN checks.

But hey, that's all par for the course: Java programmers don't think they hit NPEs, C++ programmers don't think they have ("modern") memory issues, and dynamic programmers don't think they have type problems.

What really prompted me to comment was your suggestion that it would have been better to skip the confusing checks, keep passing through an untyped object, and let some downstream expression blow up.