Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
January 18, 2023 08:59 am GMT

Typescript: best type checking for the best type safety

C# and Typescript

Before using Typescript, I used C# for many years. Put aside the ecosystem and just from the language point of view, I still think C# is the best language I have ever used, which is mainly because of the robust and powerful type system. It is really a pleasure to write code with good type inference, even for the generics, and very few annoying runtime errors.

Therefore, the moment I heard about Typescript, I knew I would definitely use it seriously for sure.

Why? because of the Hejlsberg

breaking bad

No, not either of the guy above, although the name looks similar. It is this guy:

Hejlsberg

If you have ever dived into the source code of Typescript, you should be familiar with him as he made the most commits in the repository and is also the original designer of C#, Delphi, and Turbo Pascal.

Hejlsberg-commit

Therefore, I knew Typescript would definitely inherit the good quality from C#. What I was not aware of is that It is more than that.

Exceed

Im building a new toolkitZenStack to help people build secured full-stack web apps easily. It has its own DSL(Domain specified Language) ZModel, which I chose the OSS library Langium to implement. It does a good job of parsing the language text to AST (abstract syntax tree), but it doesn't support generating code back from AST reversibly. Therefore I have to do that by myself. Luckily, I have done this using C# before. So to some degree, its like translating C# to Typescript for me.

When handling the binary expression, it needs to consider the operator precedence to see whether to generate a parenthesis. So following the old road, the first thing is to define the precedence for all the operators. In C#, it can be done by using a dictionary, in Typescript, we can simply use a const object:

export const BinaryExprOperatorPrecedence  = {    //LogicalExpr    '||': 1,    '&&': 1,    //EqualityExpr    '==': 2,    '!=': 2,    //ComparisonExpr    '>': 3,    '<': 3,    '>=': 3,    '<=': 3,    //TODO: add more operators};

To make it run e2e first, I only added the most obvious operators and left a TODO there as usual.

Then when handling the binary expression, I need to get the precedence first. The code I was about to write is as below:

code-error-1

An interesting thing happened. As you can see, there was a type error for getting the current precedence from the BinaryExprOperatorPrecedence. How come?

After checking the error message, you will know why:

Element implicitly has an 'any' type because expression of type '"!" | "!=" | "&&" | "<" | "<=" | "==" | ">" | ">=" | "?" | "^" | "||"' can't be used to index type '{ '||': number; '&&': number; '==': number; '!=': number; '>': number; '<': number; '>=': number; '<=': number; }'.Property '!' does not exist on type '{ '||': number; '&&': number; '==': number; '!=': number; '>': number; '<': number; '>=': number; '<=': number; }'.ts(7053)

It turns out that it doesnt give you the chance to throw the NotImplemented exception at all, you have to define all the operators to pass the type checking.

It might sound like extra work, but think about what will happen if later there is a new kind of operator added by someone unaware of my code. He will immediately see the type-checking error above and know he needs to also add the precedence for that operator here. Isnt this exactly the benefit of type-checking:

Fail fast, Fail often

How to get that

Simply by checking the type definition of the operator, you will see how simple and intuitive to use a union to represent it:

operator: '!' | '!=' | '&&' | '<' | '<=' | '==' | '>' | '>=' | '?' | '^' | '||';

Union type is such a great feature to make a stronger type system because it allows you to build new types out of existing ones using a large variety of operators. I think thats one reason to make Typescript shines because very few mainstream programming languages have built-in support for union. So far as I know, only Scala and Rust also support it.

Beyond

One experience I would like to share when starting to use Typescript is always to ask: can we fail faster?

Yes, we can! Simply just giving the explicit type for BinaryExprOperatorPrecedence as Record<BinaryExpr['operator'], number>

code-error-2

Then rather than see an error where the BinaryExprOperatorPrecedence is used, you will see an error right away with the below message:

Type '{ '||': number; '&&': number; '==': number; '!=': number; '>': number; '<': number; '>=': number; '<=': number; }' is missing the following properties from type 'Record<"!" | "!=" | "&&" | "<" | "<=" | "==" | ">" | ">=" | "?" | "^" | "||", number>': "!", "?", "^"ts(2739)

BTW, if you used C# like me before, you might want to define the operator as an Enum. You can still do that here without losing any benefits like the below:

enum Operator {  Add = "+",  Subtract = "-",  Multiply = "*",  Divide = "/",}export const BinaryExprOperatorPrecedence:Record<Operator, number> = {...}

Although I dont see a strong point in doing that since Union has done a great job already.

Attention!

There is an essential prerequisite for all the benefits mentioned, and I think it should be a motto for all the typescript users:

If you want to feel safe, work in the Strict mode


Original Link: https://dev.to/zenstack/typescript-best-type-checking-for-the-best-type-safety-1lcb

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To