16 ms·
TypeScript is surprisingly ok for compilers
- rtpg 3y agoTS's type system is fun but a part of me always wonders how much faster TS's compiler would be if it was written in a compiled language (assuming "good implementation", which is a big assumption!)
- supriyo-biswas 3y agoThere’s https://swc.rs/ https://swc.rs/, a Rust implementation (albeit without type checking at this time).
- mcluck 3y agoNot really a fair comparison. Stripping out types is trivially fast regardless of the implementation language. The _vast_ majority of the time taken in tsc is because of the type checking
- mst 3y agoSee the sibling comment https://news.ycombinator.com/item?id=37173313 https://news.ycombinator.com/item?id=37173313 for a type checker by the same author.
- foldr 3y agoProbably not that much faster. V8 is very good at optimizing JS code. Typescript is slow mostly because of the complexity of its type system (which is required to make it backward compatible with existing JS code).
- nikanj 3y agoProbably quite a bit faster if you do not have to instantiate a new V8 for every single file, considering how many files your average npm project has. I'm not certain if any contemporary build systems reuse the compiler process for multiple files
- foldr 3y agoWhy would it have to instantiate a new V8 for every file? You can just run 'tsc' in your project dir to compile all Typescript files.
- harrygeez 3y agoI think people often underestimate how much the TypeScript team does to make TypeScript fast and efficient. They have some of the best people in the field working on the type checker
- foldr 3y agoIndeed. I haven't checked the implementation, but I suspect that "don't spin up a separate process for every file" is an idea that has probably already occurred to them :D
- alexvitkov 3y agoYes, because it's one of the slowest compilers in the world, and its job is to compile from JavaScript to JavaScript. There are other languages with complex type systems, where the compilers have to do a lot of other additional work on top of typechecking, and are still way faster than TS. And TypeScript is always slow, not only when you do abuse its type system and require it to do complex inference - I mostly use it for "this is a number" and "this is a object with the following 4 fields" and it's still by far the slowest component in my builds usually
- madarco 3y agoPS: swc and esbuild aren't good example, because most of the speed improvements comes from the fact that they are just stripping TS-specific syntaxes to generate JS code. Also tsc is slow, sure, but only for the first run. Enabling `incremental` flag or using watch mode with `--transpile-only` usually brings compile time under 100ms, Making it practically indistinguishable from SWC or ESBuild.
- deleted 3y ago[deleted]
- harrygeez 3y agoThere's an answer from TypeScript team for your question :) https://twitter.com/drosenwasser/status/1260723846534979584 https://twitter.com/drosenwasser/status/1260723846534979584 Basically, > Let's say TypeScript takes over 20 seconds to type-check a medium-sized program. That's not usually because it's JS, it's often because of types that cause a combinatorial explosion. Also > A different runtime can afford a lot (it sounds like parallelism and start-up time in this case) but I haven't seen a CPU-bound benchmark that supports the idea of a 20x all-up speed-up.
- rtpg 3y agoI mean I get what Daniel's saying, but I kinda doubt there wouldn't be _some_ speedup. And hey, what if type checking for small programs went from half a second to 100 ms? That would be nice!
- foldr 3y agoThat's a five times speedup. I wouldn't expect a typical 'compiled' language to run five times faster than JavaScript on the kind of code you find in a compiler.
- ComputerGuru 3y agoYou are excluding startup time. Loading the interpreter/runtime and initial JIT pass is slow. For many codebases, the compilation time is actually dominated by the time it takes the interpreter to get going.
- deleted 3y ago[deleted]
- sdflhasjd 3y agoBoth swc and esbuild claim to be much faster typescript compilers in part because they're written natively, but are actually just "cheating" and not doing any type-checking at all. That's not to say they're useless though, they're good for fast hot-reload as you can have type-checking running in parallel.
- smcl 3y agoI haven't used it in a couple of years (tbh I may have to remove "full stack" and "Angular" from my CV...) but I don't recall TS compilation being particularly slow. Are people not happy with how quick it is, or do you have a particularly big/complex application you're working with?
- jitl 3y agoNotion is more than 10k typescript files and we view typecheck slowness and memory pressure as an existential threat to our codebase. Right now our typecheck needs ~12GB of heap size but the memory needs have accelerated recently.
- agloe_dreams 3y agoNX likely would do magic for you all but I imagine it is far too late to do anything about that. We had a very large Angular 10 project that we replaced with an NX Angular project late last year. We went from ~6 minute prod compile to around 20 seconds, recompile is lightning fast because it only builds your affected library. That is all without using the remote library cache feature where you can save compiled libraries to the cloud such that a user only builds the libraries they are changing ever.
- cobbzilla 3y agoCan you not split the 10k files into modules for incremental/parallel compilation?
- merlindru 3y agoWonder no more: https://github.com/dudykr/stc https://github.com/dudykr/stc Written in Rust by the (lead?) dev of SWC --- SWC (speedy web compiler) compiles TS to JS STC (speedy type checker) checks TS types
- barbariangrunge 3y agoThe reason we need to buy new hardware every few years, and fill landfills with our old stuff, is because of ideas like “let’s use JavaScript to write a compiler!”
- agloe_dreams 3y agoNo lie, the M1 was a game changer for large Typescript projects.
- conaclos 3y agoPerformance of the TypeScript compiler has greatly improved in the last versions. The future isolated declaration mode promises great improvements: until 75% of reduction in compilation time! [0] [0] https://github.com/microsoft/TypeScript/pull/53463#issuecomment-1660720127 https://github.com/microsoft/TypeScript/pull/53463#issuecomm...
- agloe_dreams 3y agoThe type system is fun right up until you are using it to its full ability in generics..then you look at the 5 lines of 'type logic' you spent the last day debugging and ask yourself how you got here.
- svachalek 3y agoI've definitely seen it suck people into a black hole. It works best when you think of it as a form of documentation with a benefit of enforcement, rather than trying to use the type system to prevent every conceivable bug. Massive unreadable types don't help programmers write better code.
- thebears5454 3y agoPretty true lol chaining something together that ends up looking like this: <<<<<<<<T>>>>>>>> Is basically a guaranteed headache
- cxr 3y ago> part of me always wonders how much faster TS's compiler would be if it was written in a compiled language TypeScript's compiler _is_ written in a compiled language. (I think you are using "compiled" here as a euphemism—one that doesn't help anyone.)
- lmm 3y agoIs that really suprising? Typescript is yet another language that has, kicking and screaming, picked up most of the ML featureset. I'd expect it to be, well, fine; the lack of real pattern matching is a pain, so it's going to be inferior to OCaml, but fine, no different from using C# or Swift or Dart or Kotlin or something of that ilk.
- thebears5454 3y agoNaively, I would have guessed it being harder than C#
- paddim8 3y agoI would think C# is easier for this due to having proper (and very convenient) pattern matching.
- zkldi 3y agoC# doesn't have anything resembling proper pattern matching. C#s pattern matching is akin to a marginally improved switch or if/else-if chain. C#s pattern matches aren't exhaustive, and the compiler doesn't properly check them at all.[0] There are no proper sum types in C#, so 80% of the point isn't even there. enum Season { Spring, Summer, Autumn, Winter } ... int PatternMatch(Season season) => season switch { Season.Spring => 1, Season.Summer => 2, Season.Autumn => 3, Season.Winter => 4, // compiler can't prove the above code is exhaustive because no proper sum types // compiler needs nonsensical branch here that diverges _ => throw new ArgumentException("Invalid enum value for command", nameof(command)), };
- hardware2win 3y ago>compiler can't prove the above code is exhaustive because no proper sum types Thats because c#s enum is not "closed" https://github.com/dotnet/csharplang/issues/3179 https://github.com/dotnet/csharplang/issues/3179
- catsarebetter 3y agoThat is surprising, I would've thought that TS would have more overhead because of the interfaces adding extra weight. Makes me wonder what else TS could apply to. Language parsing, maybe?
- recursive 3y agoAt runtime, interfaces have zero weight. They get completely compiled out.
- newusertoday 3y agoit isn't, i have codebase in golang that is much larger than typescript and it is pleasant to work with lsp and compiles smoothly. With typescript i have to turn off lsp and even after that it takes long time too compile. There is a reason why people are writing typescript compiler in rust.
- IshKebab 3y agoHe's talking about compilers for small languages. The Typescript LSP works fine on very big projects like VSCode so I think you'd need an enormous language like C++ or Rust before you'd run into those limits. But still, I think I'd rather use Rust. I'm pretty sure the code would be nicer (e.g. no need for the explicit tag field or for the visitor hack).
- baq 3y agoI see ‘TS server has been restarted 5 times in 5 minutes’ daily.
- iends 3y agoI don’t see this and I work on large enterprise websocket server with almost a million daily active users. In fact, I think we had 100% up time for the last 12 months except for a few AWS outages. Codebase is 10 years old and was CoffeeScript -> ES6 -> TypeScript.
- baq 3y agonono, I mean the LSP in vscode. prod deployment is unrelated.
- tejinderss 3y agoThe author is no stranger to rust (he’s creator of rust-analyser). The reason why he’s pitching typescript here is due to its high level nature and doesnt have to deal with memory management, low level integer types etc.
- zelphirkalt 3y agoBut to what end? For a compiler, there is no need for all the overhead of npm (usually), ts, tsconfig, package.json bundler and bundler configuration, to get something usable, unless one really wants a JS thing at the end to run in the browser. I imagine even some webassembly tool chains may be shorter.
- deleted 3y ago[deleted]
- meheleventyone 3y agoThe article answers this question. The author is using deno which is a single binary that uses TypeScript without all of that.
- domlebo70 3y agoTo OP: You could avoid the visitor by using an IIFE style switch using the run utility fn: export const run = <T>(f: () => T): T => f(); Now you can go: const inferred_type = run(() => { switch(blah) { ... } })
- mgreenw 3y agoExactly! I wrote a post about this pattern: https://maxgreenwald.me/blog/do-more-with-run https://maxgreenwald.me/blog/do-more-with-run
- domlebo70 3y agoYeah this is where I heard about it :P I showed all my dev friends, and our minds were equally blown away by the obviousness of it.
- gavinray 3y agoYou don't even need to do this much, you can just invoke the function and let the return type be inferred. const inferred = (() => "Hello")() // Inferred as "string" If you really don't want to use an IIFE because you don't like the "()" at the end, you can just: const myFn = () => { switch(...) } const inferred = myFn()
- dna_polymerase 3y agoIt sure is. For anyone looking into Compilers and just starting out, I recommend this book: https://keleshev.com/compiling-to-assembly-from-scratch/ https://keleshev.com/compiling-to-assembly-from-scratch/ The author uses a TypeScript subset to write a compiler to 32bit ARM assembly and explains that it almost looks like Pseudocode, so it is very accessible. A sentiment I can get behind, despite avoiding it in any case possible.
- jabits 3y agoThanks for the ref. Looks very interesting!
- Bognar 3y agoI would also _highly_ recommend Crafting Interpreters: https://craftinginterpreters.com/ https://craftinginterpreters.com/ The book is split into two parts. The first implements a language interpreter in Java, and the second implements the same language by building a compiler to byte-code and also implements a byte-code VM in C. Every line of code in the implementation is referenced in the book.
- jjice 3y agoAnecdotally, I love the reference to the classic "Dragon Book" that this cover makes.
- gavinray 3y agoThis looks great -- thank you for sharing!
- hardware2win 3y agoIve been writing compiler in C# and I dont see anything fancy here except union type Ive personally decided to avoid visitor pattern bloat and Im waiting for closed enum feature in order to have compile time exhaustive check
- AgentME 3y agoI find myself really missing union types when I'm in a language that doesn't have either it or Rust-style enums. The usual alternatives are awkward: either one class representing the union type containing N nullable properties with the documented condition that only one property is ever expected to be non-null, a condition the type system isn't able to check or make use of, or multiple classes extending a common class, which feels really heavyweight. Both solutions require some duplication or clever composition if you want several different overlapping union types.
- gr__or 3y agoVery much apropos: Going between Rust and TS it is painfully obvious how much sth like tagged enums are missing, which can also be seen in this post. I know of this [1] proposal for ADT enums which looks like it has stalled. Anyone know of other efforts? [1] https://github.com/Jack-Works/proposal-enum/discussions/19 https://github.com/Jack-Works/proposal-enum/discussions/19
- dcre 3y agoI find discriminated unions work ok for similar use cases.
- tomp 3y agoAs the post nicely demonstrates, TypeScript is definitely not OK for compilers (and not surprising at all!) It doesn't even have destructuring pattern matching! At this point, even Java is better [1]. [1] https://github.com/tomprimozic/caya/blob/master/src/caya/Interpreter.java#L250 https://github.com/tomprimozic/caya/blob/master/src/caya/Int...
- dom96 3y agoPattern matching is definitely not a requirement for a language to be good for writing compilers in.
- deleted 3y ago[deleted]
- deleted 3y ago[deleted]
- vlakreeh 3y agoWith how powerful the type system is you can implement pattern matching via a library pretty convincingly, https://github.com/gvergnaud/ts-pattern https://github.com/gvergnaud/ts-pattern is definitely the go-to. That being said pattern matching is hardly a requirement for being ok for implementing compilers.
- rustybolt 3y agoI really don't know what the author means by "language-centric" vs "implementation-centric" and "production-ready"...
- matklad 3y agolanguage-centric: the language you are compiling is flexible (you are also the language designer), you want to build the best possible language implementation-centric: the language you are compiling is mostly fixed and/or defined by an external entity. You want to build the fastest compiler for this language, which emits the fastest code.
- frozenport 3y agoGut reaction is to use LLVM for everything...
- deleted 3y ago[deleted]
- matt_kantor 3y agoWhat does that have to do with the compiler's implementation language? A written-in-TypeScript compiler can emit LLVM bytecode just like a compiler written in any other language.
- harha_ 3y agoI'm sick of seeing overly complicated TypeScript code. Mainly overly complicated types.
- deleted 3y ago[deleted]
- LeanderK 3y agocompilers are really complicated pieces of software. There's a lot of complexity even in small routines. I would expect to encounter complicated types in there, they capture (while complex) intent and meaning. It's not the frontend glue-code kind of code. The inherently hard problem makes is fun for people who like to solve small puzzles.
- auggierose 3y agoNot that surprising: https://news.ycombinator.com/item?id=37039443 https://news.ycombinator.com/item?id=37039443 Of course, that one has been downvoted to -4 (as of now).
- grumblingdev 3y agoTypeScript is an incredible language in general. The fact that Functions are Objects that can have properties/methods is supremely undervalued. Are there other languages that do this so nicely? It's the perfect blend of OO and functional. Programming is mostly about gradually figuring out the right design I find. JS/TS let's me evolve things naturally without big rewrites. function foo() {} function bar() {} function baz() {} const commands = [foo, bar, baz] // Run commands commands.forEach(x => x()) foo.help = 'This does something' // Describe commands commands.forEach(x => console.log(x.help)) // Add some state via closure. const config = {} function foo() { config.blah } // Add some state using partials. function _foo(config) { config.blah } const foo = foo.bind(null, config) I can flexibly do what I need, without ever having to define a class like `Command`, which I probably don't even know what it should be yet. This premature naming of things in OO creates so many dramas. It avoids things like `VideoCompressor#compress()`, `VideoSplitter#split()`. You don't have to think: what state belongs in which class...you just call the function and pass it what it needs.
- karmakaze 3y agoThe same is achieved with Java's use of single-method-interfaces. It doesn't matter what the method is called, it can be used in function contexts without referencing the specific method name. I'm not sure I'd like my functions to have properties (which gives them state and can alter what calling it does with the same arguments). A big benefit of FP is getting away from OO states. Perhaps the problems I work on aren't complex enough to benefit from them, or I simply make objects from classes.
- TOGoS 3y agoThe properties don't have to be 'state' per se; they can also be used to store metadata about the function. e.g. you could have a function with 'domain' and 'range' on it, or an 'integrate' method.
- karmakaze 3y agoIf the result of `integrate(...)` depend on `domain` and/or `range`, what would you call that other than 'state'? Or if `integrate(...)` doesn't reference `domain`/`range` what's the use of it being there? Or as I'm interpreting this, is sort of like documentation or information that could be used at runtime for code generation. Basically a shorthand for composing the function with its metadata. The nice thing about separation is that you know that when calling the function, there's no possible way for it to reference the metadata that is associated with it because it's composed externally.
- boredumb 3y agoevery day we stray further from God
- metalforever 3y agoIt’s just not the right tool for the job .
- postalrat 3y agoIt's the perfect tool for the job.
- metalforever 3y agoIf all you have is a hammer, everything begins to look like a nail.
- postalrat 3y agoWhy isn't it the best?
- paxys 3y agoThe result shouldn't be all that surprising considering the TypeScript compiler is written in...TypeScript. So TypeScript as a ML is already battle tested and is in heavy production use every day.
- deleted 3y ago[deleted]
- andromaton 3y agoCan I humbly add "Anders Hejlsberg" to the list of reasons why none of the commenters seem surprised?
- andrewcobby 3y agoAs some how is writing a compilter in TS, I agree it's not too bad. I started with Deno like the author but ended up switching to Bun which, despite some rough edges, I'm enjoying more than Deno and it's _very_ quick! (My main niggle with Bun is the test reporting, but it's getting the job done) For standard parser generator frontend, Ohm-js[1] is quite pleasant. I wouldn't recommend anyone reviews the offical tsc compiler, it's huuuge - instead there's mini-typescript[2] (in particular the centi-typescript branch[3]) which does a nice job illustrating how tsc is working. [1] https://ohmjs.org/ https://ohmjs.org/ [2] https://github.com/sandersn/mini-typescript/ https://github.com/sandersn/mini-typescript/ [3] https://github.com/sandersn/mini-typescript/tree/centi-typescript https://github.com/sandersn/mini-typescript/tree/centi-types... Looking forward to GC an DOM access in WASM.
- cobertos 3y agoCurious. What made you switch to Bun?
- andrewcobby 3y agoI know this is probably blasphemy at this point, but I actually wasn't enjoying package management in Deno (or lack thereof). I've been an avid user of the NPM ecosystem for a long time (10+ years), it's entrenched deep in me and I don't mind it haha! In particular with Deno, it was trying to remember all the dependency URLs, and not wanting to duplicate them - every time I wanted to read a file and I had to lookup the std library URL for the umpteenth time. The package discovery isn't as good, and documentation was too different (why is everything in a file called mod.ts? why do I need to see all of the files in a dependency?). This led to the recommended advice of having a dedicated TS file where you pull all your external dependecies in and then re-export them locally. This felt worse than package.json (read: clunky not having a first-class system to list external dependencies). And this is in a project with minimal dependencies too - yet it still frustrated me. Plus my editor had no type info for any external imports. I then discovered import maps, but that was still hit and miss in my editor (Neovim mind you) with code completion, made worse by not having a clear way to import ohm-js. Not to mention having to explicitly list `--import-map` for every command and forgetting too often and then wondering why my code wasn't working. Just feels like it's on a roundabout path to reinventing package.json anyway, just with distributed packaging by default? I'm not particularly concerned with security either, so having to list `--allow-x` and `--allow-y` got tiring very quick. At this point, I just felt like Deno was constantly trying to stop me from getting stuff done. I ended basically ditch most of the new stuff in Deno and was basically treating it a ts-node (thanks to the new NPM/Node compat feature) - but it had me off side now. I knew Bun.sh has some rough edges in terms of compatibility, etc (which is fine as it's pre-v1)... but for writing a compiler: I'm mostly writing everything from scratch and just need to read/write from the filesystem which is definitely stable and well documented. Switching to Bun was painless and all of a sudden everything Just Worked™, ran faster and I can get back to actually working on my compiler. The only problem I've had with Bun is not being able to tweak the output the results of `bun test` (e.g. if one of the first tests fail, I have to keep scrolling back up through my big list of passing tests to see why it failed) - if Bun had a better error summary at the end of the test output I'd be set! TLDR; As someone using NPM/node for over a decade, nothing in Deno "just worked" for me and instead blocked me at every turn and the lack of package management turned me off. I know there are sound and objective reasons for design decisions in Deno, but none of those things matter much _to me_. Bun, on the other hand, is intended as a drop-in replacement for Node, but faster and with native support for TypeScript which ticks all the boxes I need for this particular project. (FWIW I tried switching a Remix project to Bun and it didn't work and stuck with Node).