DEV Community

Discussion on: Typescript can be confusing

Collapse
 
peerreynders profile image
peerreynders • Edited

Please observe:

> ['1.2', '2.3', '10.11'].map(Number)
(3) [1.2, 2.3, 10.11]
Enter fullscreen mode Exit fullscreen mode

versus

> ['1.2', '2.3', '10.11'].map(n => Number.parseInt(n))
(3) [1, 2, 10]
Enter fullscreen mode Exit fullscreen mode

With software the "devil is always in the details".

Martin Fowler

Any fool can write code that a computer can understand. Good programmers write code that humans can understand.

Refactoring: Improving the Design of Existing Code, 2nd Edition p. 10 (2018)

With that in mind when I see parseInt in code that I review, I have to conclude that floating point values are not welcome and that the range of values from Number.MIN_SAFE_INTEGER to Number.MAX_SAFE_INTEGER is considered sufficient for the purpose of the functionality.

Ultimately I prefer:

const toInteger: (v: string) => number = (value) => Number.parseInt(value);

function doSomething(...values: string[]): number[] {
  return values.map(toInteger);
}

console.log(doSomething('1.2', '2.3', '10.11'));
Enter fullscreen mode Exit fullscreen mode

Clearly it's more verbose but the added side benefit is that you aren't at the mercy of the JavaScript engine realizing that it can just reuse the same function - rather than creating a new anonymous function every time doSomething is invoked. (JavaScript performance is notoriously unpredictable and inconsistent).

In this way I actually find that TypeScript is an impediment to refactoring.

While it seems perfectly capable of inferring a function type when the function is inlined, it needs explicit type declarations once you factor it out (something ReasonML/ReScript doesn't tend to have trouble with) which also means that the source becomes more "noisy".

In my personal opinion inline functions are more imperative because they focus on the "how" of the functionality. With named functions you have an opportunity to be more declarative because the name can relate to the "why".

In pure ECMAScript I prefer:

function toInteger(value) {
  return Number.parseInt(value);
}
Enter fullscreen mode Exit fullscreen mode

because I find function declaration hoisting useful - it gives you more freedom of how to order things within a module.

But in TypeScript:

function toInteger(value: string): number {
  return Number.parseInt(value);
}
Enter fullscreen mode Exit fullscreen mode

The equivalent to ECMAScript code exists in the "value space" - TypeScript types exist in "type space". With function toInteger(value: string): number both are conflated (in the tradition of languages with a C-style syntax).

So in TypeScript I often find myself trading-off hoisting:

const toInteger: (v: string) => number = (value) => Number.parseInt(value);
Enter fullscreen mode Exit fullscreen mode

to separate the "type space" function type (v: string) => number from the "value space" definition const toInteger = (value) => Number.parseInt(value);.

Collapse
 
simoncodephere profile image
Simon Pfeiffer

thanks for the insight! will keep that in mind for floating-point numbers :)

Collapse
 
lico profile image
SeongKuk Han

Thank you! OH Yep,,, I didn't think about floating number.
Anyway, I agree with your opinion.
Thanks again, I learned a lot from your comment.