Readit News logoReadit News
scotttrinh · a year ago
Hey, article's author here! Happy to answer any questions, or poke at this general problem with anyone who is interested. Understanding the type checker and its performance is my current personal focus and I find it helpful to bat around ideas with others.
timcobb · a year ago
Hey thanks so much for writing this up. This is a great post! I've only had time to skim the article, so my apologies if you covered this and I missed it: have you investigated whether specifying expression/function return type affects performance? I work on something of a large codebase, and I wonder if whether we annotated our returns, type checking would be faster.
scotttrinh · a year ago
Yeah! Making explicit return types, especially of public functions, is a good practice to follow. I'd say the main reason isn't performance, though, but rather to ensure you have a stable public API for your module.

How much it actually speeds up the type checker depends on how hard it is for the type checker to infer the return type. And that depends on the return expression, but I don't think there is a single hard and fast rule here. But, if you already have a named type for the return value of the expression, I would absolutely annotate it explicitly when possible. Sometimes the inferred type won't be really the type you intend, and there might just be a more clear type you want to use for communication/documentation purposes.

tarasglek · a year ago
I really wish to find ts tooling that would show me deltas in ts checker memory usage and tie them to diffs in ci
kevingadd · a year ago
We accidentally had a regression slip into our TS once that made it take over 7 seconds to typecheck a file, and that was surprisingly painful to diagnose. It meant our CI builds were slower, our local builds were slower, and the language server (in VS code, sublime, etc) would just randomly go unresponsive while editing. If there were tooling to track deltas in that per-file we would have noticed it immediately.
MonstraG · a year ago
Can you share what was the problem, just in case we have it too?
zamadatix · a year ago
Sometimes I wish something akin to Dart (but probably not Dart) had taken off instead of the TypeScript approach. I.e. a JS based language that broke a few things to get types but largely ran on the same VM and could still easily be transpiled in the meantime. Avoid the whole "separate syntax on top of the way the underlying syntax behaves" set of logic.

I suppose WASM enables layered languages like AssemblyScript comes close in many ways but it's also a bit too separated from the primary webpage use case.

zarzavat · a year ago
The TypeScript approach has given us one of the most interesting programming languages to appear in recent times, a language that is completely committed to structural typing.

The Dart approach just gave us yet another unimaginative nominally-typed language.

TypeScript is complex, but it’s also incredibly cool if you’re into compilers. Engineers do their best work when there are limitations imposed, in this case the need to add types to JS.

adamc · a year ago
I learned typescript for a project I spent 9-10 months on. It wasn't that hard to learn but... ugh. On many levels. There are problems where it's typechecking is helpful (enough) for, but the degradation in readability was quite noticeable, and the fact that its abstractions only work at compile time was an endless thorn in the side.

Not a fan.

shepherdjerred · a year ago
Zod (and similar libraries) solve that last issue: https://zod.dev/

You can, for example, declare your type and check at runtime if some object matches the type.

giraffe_lady · a year ago
In an alternate universe ReScript was this.
throwitaway1123 · a year ago
It's a shame that Reason fragmented into Reason and ReScript. I remember there being a ton of excitement around the project when Jordan Walke (creator of React) announced that he was working on a JS successor.
joseferben · a year ago
excellent article! my approach is to to break down larger bits into smaller monorepo packages with turbo repo where each package builds itself and the task graph is managed by turbo. the drawback is that watching across local packages doesn’t work out of the box.
brundolf · a year ago
I've come to believe that sufficiently advanced type inference is indistinguishable from an interpreter

...which has the implication that what TypeScript is actually giving us is a REPL. Our code is increasingly "evaluated" by our IDE, in our hover-overs

I think this is a major reason people like TypeScript so much

shepherdjerred · a year ago
TypeScript's type system is turing complete. You might find this interesting: https://github.com/type-challenges/type-challenges
codeflo · a year ago
These days, TypeScript is effectively nothing more than a high-powered linter. The performance of this linter is so bad that we need to structure our code in a specific way so that we can still afford to run the linter.

Of the performance tips at the end, the interface vs. intersection type one is the suggestion I find the most annoying. That’s because it’s the most common pattern, and using interfaces is conceptually a lot less clean. It’s terrible that a linter effectively forces you into writing worse code.

I really wish the TypeScript team got their act together and fixed the performance of their linter somehow. Finding clever optimizations, porting to Go/Rust, whatever is necessary. (3rd-party reimplementations won’t do: they’ll never catch up with a corporate-funded moving target.)

rafaelmn · a year ago
> These days, TypeScript is effectively nothing more than a high-powered linter.

That's a bad take - typescript enables tooling like refactoring/navigation/completion that goes far above a linter. Development tools are just better with typescript vs JS.

adamc · a year ago
That's ignoring all the negative effects of using typescript.
bk496 · a year ago
There is a new type checker called Ezno that is written in Rust and is a lot faster [1].

I have been tracking PRs like [2] that change the definitions to better be optimised by V8. But the effects are only ~30% and not the 50x that might be achievable by native.

[1]: https://github.com/kaleidawave/ezno/actions/runs/10299707325 [2]: https://github.com/microsoft/TypeScript/pull/58928

IshKebab · a year ago
How fast does tsc process that input though? I would be very surprised if you can get to 50x faster - that's Python territory and JavaScript isn't that slow. 10x maybe?
bluepnume · a year ago
At this point TS is a turing complete language.

Complaining that you have to tune it for performance is like complaining that your runtime code isn't automatically maximally performant without a little tuning.

shepherdjerred · a year ago
TypeScript has been turing complete for a very long time. You might find this interesting: https://github.com/type-challenges/type-challenges
rtpg · a year ago
Complicated types are effectively little Prolog programs, doing a bunch of very useful and helpful checks to make sure that your code does what you expect it to.

I do wish that Typescript would offer some tools to make it more ergonomic to write performant unifying code (I kind of despise conditional types, especially when you then use it to create the partially valid types by resolving to never). But I think it would also be very helpful to get people to understand that your types are their own little program that run and have performance characteristics. It's not magic!

There's been some handwaving about performance not being due to it running in JS (because at the end of the day unification is unification is unification and it takes time), but looking at the Typescript codebase in general and poking at it, I can't help but wonder how much of even the heavier stuff is "death by 1000 cuts" on that front.

scotttrinh · a year ago
This really was solidified by going through the course at https://type-level-typescript.com since it involves learning the type-level language of TypeScript and solve little puzzles. Doesn't really address performance much, but I think having a working-level understanding of what the type-checker is doing when it's "solving" your TypeScript type-level programs is an important prerequisite for having some intuition about type checker performance.
vmfunction · a year ago
> I do wish that Typescript would offer some tools to make it more ergonomic to write performant unifying code (I kind of despise conditional types

Maybe give https://gcanti.github.io/fp-ts/ a go?

xboxnolifes · a year ago
With such a broad definition of linter, wouldn't the type systems of all statically typed languages just be high-powered linters?
zachrose · a year ago
I’ve worked on several TS projects that don’t type check but still “compile” (emit non-TS JavaScript). To me that’s the difference between a linter and a compiler, and I wish those projects had stopped compiling when they could no longer type check.
codeflo · a year ago
No, in most static languages, the type system influences code generation.
epolanski · a year ago
What's the alternative?

Anything you name has either less support or different cons.

Quothling · a year ago
You can use something like JSDoc and achieve basically the same thing, but it's very likely that your developer experience will be way worse as you sort of point out. If you're a VSC enjoyer your tooling will be absolutely horrible compared to the Typescript tooling. We use Typescript as our general JavaScript "language" but most of our internal libraries are written in actual JavaScript for performance reasons. They key difference is those libraries are worked on by far fewer people.
mdhb · a year ago
With WASM starting to become a thing we are no longer limited to just JavaScript and things that compile to or transpile to JavaScript.

It’s early days there but with the JS ecosystem being the mess that it is I’m actively interested in finding alternatives to at least evaluate.

One approach I’m enjoying so far is Dart which has two relevant compilers (I.e Dart to JS and Dart to WASM) but they have the advantage that you can just use Dart like normal which is a clear 10x improvement over writing either JS or TS and you only have to worry about the specific layers where you need to interop with JS code and you can wrap that up in really nice ways.

For example here’s an example of Dart interacting with browser APIs: https://github.com/dart-lang/web/blob/main/example/example.d...

k__ · a year ago
Technically, it is more than a linter, as enums don't have a direct representation in JS.
Narhem · a year ago
If I had to guess working with parsing xml is more complicated than traditional code.

Can’t find the links but one of the reasons I’ve seen people move away from xml has been due to the speed in parsing when compared to json or csv.

seanmcdirmid · a year ago
Doesn’t the type checker have to run in JavaScript to fit into VS code? A C# implementation would be much faster, no need to go native with C++/Rust (not sure speed in Go would really compete).

The big issue is dealing with a structural highly expressive type system, the language of implementation is only going to be a constant slow down (but that constant can be large).

Quothling · a year ago
It depends on which typescript toolset you use, but generally speaking you're probably riding on mix of C++ and Javascript if you're using VSC. VSC itself uses C++ in it's core components since that is how electron works. Similarly the language server and tooling for both Typescript and Node are build with C++. If you're fancy and use Bun you're running on Zig. Eslint itself runs on Javascript, but the parser it uses feeds it something called an abstract syntax tree, and different parsers will do this differently.

So the relatively simple answer would be no, it would not be faster with C# (or Go which would likely have a similar speed to C#).

mook · a year ago
Don't a lot of the linters run externally via language servers? I'm pretty sure the rust linters are rust-native for example.