This is why TypeScript restricts + operator
Pragmatic Maciej
Posted on March 9, 2020
The source of motivation
Lately I found myself in Twitter discussion when there was an argument that TS wrongly blocks flexibility of +
operator. As in JS we can use this operator where left and right argument is really anything 🛸, of course we all know that most of these use cases have no sense, as what will be the output of adding boolean and an array? So most usages of +
in JS are nonsense, and work only in order to not throw runtime exception. From these with sense are addition on numbers and concatenation on strings, but also very popular is to concatenate strings and numbers, where number will be converted to string. And we will focus on exactly such usage of +
in TS.
The restriction
Below totally fine JavaScript operation is being blocked by TypeScript
function add(a: string | number, b: string | number) {
return a + b; // compilation error
}
Why is that? Why we just cannot be flexible like we are in JS, as TS is only superset of JS, everything should work the same? Lets recap that JS is dynamically typed and loosely typed language, the second means - we can use operators with any kind of data, and the language will figurate out somehow how to change apples🍎 to bananas🍌, even with most strange 🤪 result.
TypeScript Specification: The binary + operator requires both operands to be of the Number primitive type or an enum type, or at least one of the operands to be of type Any or the String primitive type
So out of box TS will not allow us to do boolean
+ array
, as nobody wants to do such (I hope 😁). But also “surprisingly” will not allow for something many would say as just flexible and useful.
r = a + b | a: string | number | a: string | a: number |
b: string | number | r: string | number🛑 | r: string🟢 | r: string | number🛑 |
b: string | r: string🟢 | r: string🟢 | r: string🟢 |
b: number | r: string | number🛑 | r: string🟢 | r: number🟢 |
a - first operand type
b - second operand type
r - result type
Allowed by TS - 🟢
Disallowed by TS - 🛑
Lets center our attention at correlations not allowed by the language 🛑. All of these have common thing, the result is not primary type, it is union string | number
. In other words TS is not allowing for the operator to return not a primitive type, another observation is that allowed operations have or string literal as one of operands, or have two numbers. What is this all about?
The explanation
The problem with not allowed cases is that the operation is unpredictable, it uses two different forms of joining elements, first numbers addition, second string concatenation.
- number + number - addition
- string + anything other - concatenation
It depends from the input which method will be chosen, it means that for “1” + 1
function will perform a different operation than for 1 + 1
. Take a look at the result of this function in pure JS:
add(“1”, 1) // “11”
add(1,1) // 2
add(1, “1”) // “11”
As we can see above, such function will differently behave for different types of data, and the result will be always either string or number, we never know if it will add, or concat. The good name for such a function would be addOrConcat or guessWhatIWillDo 👌 as this is how it behaves.
On the other hand why TS is allowing for using +
for string and number(or anything) at all? Such operations are allowed because we always have one and only one operation which is concatenation, number will be converted to string and concatenate after. That is to say, our function will always return string
and we can reason about the result. To that end, TypeScript is more strict because any usage of function which will or concat or add is just an issue, nothing more, we never want such, believe me.
The better alternative
If we want to concatenate numbers also, then use string template, in that way we are safe that operation will always be conversion -> concatenation and never number addition
function concat(a: string | number, b: string | number) {
return `${a}${b}`;
}
concat(“1”,1) // “11”
concat(1, 1) // “11” 🏆🎉
// and if you want to add numbers do for it different function
function add(a: number, b:number) {
return a + b;
}
And now TypeScript doesn’t complain as this code has a sense. It is monomorphic in the behavior. This is kind win-win solution 🎉
Yes examples are trivial and nobody will abstract
+
in such a way, but we can imagine that our real function will do such operations with additional logic
TypeScript is good for you
All things considered TypeScript is doing a good thing, prevents runtime errors, and the only usage of such construct would be exactly runtime error, such flexibility can be considered only as a code smell smell 🦨.
Also consider that giving a function not needed polymorphic behavior also can be not a good idea, I wrote about that more in the article - Function flexibility considered harmful.
Posted on March 9, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.