11 ms·
TypeScript as fast as Rust: TypeScript++
- janpaul123 5y agoHi! Author here. Really curious what you all think. It's a pretty crazy idea but maybe there's something to it! Hacker News is always a special place for ideas like this — you never know what you're gonna get ;) — so I'm looking forward to discussing this with y'all!
- mc4ndr3 5y agoWhy not write Rust directly, and target WASM for client side environments? I support efforts to tune existing tech stacks. But there is such a more direct path to performance, reliability, etc etc by dropping (alt)JS entirely now that anything can compile to WASM.
- janpaul123 5y agoI agree; that is what we built the Zaplib framework for. But the reality is that there are tons and tons of existing JS/TS codebases out there, and it'd be nice to have an incremental speedup path for them too.
- michael_j_ward 5y agoIn the article https://zaplib.com/docs/blog_ts++.html#best-of-both-worlds https://zaplib.com/docs/blog_ts++.html#best-of-both-worlds
- deleted 5y ago[deleted]
- mattgreenrocks 5y agoThe TypeScript++ example looks like the type of code that JITs typically like. Python has Numba, where users affix a @jit annotation to functions to request that. It works pretty well in my experience. However, at this point, I'm mostly of the opinion that langs need some notion of mechanical sympathy if they want to be used for high-performance. JS engines probably have tens of millions of dollars of dev effort poured into them by now, yet they get soundly beaten by WASM, which is much younger. The conceptual foundation matters a lot. That said, there's probably a nice space for an ergonomic, reasonably fast PL that sits between Rust and JS.
- isaacimagine 5y ago> JS engines probably have tens of millions of dollars of dev effort poured into them by now And JS engine JIT codegen modules are put to good use compiling wasm.
- janpaul123 5y agoGood points. I agree that choosing the abstractions wisely is key here.
- discreteevent 5y agoThat was the main motivation behind the inventors of dart. They got frustrated with all the hacks they had to put into V8 just because JavaScript was so mechanically unsympathetic. You can hardly blame then because they originally came from a background of developing smalltalk and self VMs.
- emteycz 5y agoIsn't JS much more Smalltalk-ish than Dart? I don't have much experience with Dart, but it always seemed like a very OOP language to me (and not in the "message passing" meaning).
- mattgreenrocks 5y agoThis is super interesting to me as a VM geek. I'd written off Dart as "Google's Swift" for the most part, but this makes a lot more sense. Thanks for the comment.
- notriddle 5y ago> That said, there's probably a nice space for an ergonomic, reasonably fast PL that sits between Rust and JS. Other than not running natively in web browsers, isn't that language called Ocaml?
- scns 5y agoIs it still possible to use the ReasonML syntax for native binaries?
- novocantico 5y agoThe main feedback I have is that I wanted to read this near the end of my lunch break, because I've been interested for years in the idea that a reasonably-restricted subset of TypeScript (who needs all that dynamism) can almost definitely be compiled to machine code and be made super fast, faster than V8 -- so I was looking for a TLDR in this article and spent a couple minutes looking for it and then just reading whole paragraphs and still don't really know what you've got here.
- mattdeboard 5y agodang imagine how much more leisurely you could've read the article if you didn't also take some of your lunch break to write this comment!
- janpaul123 5y agoGood feedback; I should add a TL;DR. Basically this is a proposal to create a language that sits somewhere between Typescript and Rust, and which you can incrementally adopt if you already use Typescript.
- tomxor 5y agoYour example really clicked with me, having written a substantial quantity of JS in a "memory managed" way to avoid GC, and avoiding memcpy by passing buffers in place of pointers. My initial instinct is that putting such considerations behind a preprocessor syntax has the disadvantage of making them more opaque and magic, and that I personally would continue to do such things more explicitly - which I realise is an ironic thing to say about an untyped, interpreted language, it's probably just the little pseudo "grey beard" on my shoulder which occasionally whispers silly things about "building their own computer out of rocks and twigs". I guess the point is that there's a balance to the cost vs benefit: making it more convenient (what you call ergonomics), making it more accessible to those that were less likely to manually use that technique, and perhaps resulting in an average of more performant code; but at the cost of more obscurity, and more "magic", perhaps resulting in less educated programmers. I'm probably overthinking it. Ultimately this argument even applies to for loops, but it's worth being conscious of that cost - it's likely a good idea.
- janpaul123 5y agoYeah I hear you; and even allude to that in some parts of the article, like this part: > Another idea to make Typescript–– a bit less restrictive and more ergonomic, would be to use reference-counting of objects, and then try to optimize most away using compile-time reference counting as pioneered by Lobster. This gives most of the advantages of manually managed memory, but makes the ownership model much easier to reason about. It is however slightly less performant, and more importantly, makes performance less predictable, since it becomes more reliant on compiler cleverness. Even Rust can hide actual performance behind abstractions, and I've already been bitten by that a few times.. Not sure what the right solution is here, but I agree that it's super important to think about this when designing a language like this.
- triyambakam 5y ago> having written a substantial quantity of JS in a "memory managed" way to avoid GC, and avoiding memcpy by passing buffers in place of pointers. What projects were you working on that required that? It sounds interesting
- pjmlp 5y agoLove it, you should check the Typescript compiler used by MakeCode as well. Maybe there are some ideas there.
- transitivebs 5y agoReally interesting article. I was doing some high-level research on a related question the other day: https://transitivebullsh.it/webassembly-research-9e231b80e6d54306adceff38ff22954c https://transitivebullsh.it/webassembly-research-9e231b80e6d... I know you've responded to a few people on here that Assemblyscript doesn't address the main issues you're talking about in the article, but honestly it does have a lot of things going for it. I'd love to see a more direct breakdown of this space that explains why prior works like assemblyscript, nectarjs, walt, etc all fall short, and where an ideal solution would need to do better. ^^ this is the type of thing I'd love to chat about if you're interested btw
- janpaul123 5y agoNectarjs is interesting; I wasn't aware of that. How did it manage to avoid (for example) a garbage collector without making changes to the language?
- Syzygies 5y agoYou might want to reconsider the ++ naming convention. Many people capable of a critical appraisal of C++ wouldn't want an association with C++. The prejudice that your name triggers is that you don't understand what others think C++ got wrong, and you're likely to be making the same mistakes. It doesn't look like you're making the same mistakes, but why trigger that prejudice?
- vanderZwan 5y ago"Why can't we have a language that compiles to highly optimized JS?" is definitely something that has crossed my mind, especially in situations where I reached for faking structs with TypedArrays. As soon as that gets more complicated than something like an array of x,y,z points things get difficult (not to mention almost impossible to debug) really fast. I suspect that if you could come up with an easy way to express a large tree really efficiently in a TypedArray in a relatively generic way, that you already will do better than most attempted solutions here.
- deleted 5y ago[deleted]
- gigel82 5y agoWe briefly toyed with the reverse idea; wanted to share some code between web and native, and TypeScript / C++ already look very similar - what if we invented a subset of TypeScript that can be simply translated into C++ (as long as you stick to static use and types). Worked ok for a while but broke down real quick once we brought in external npm dependencies. Now we're just embedding V8 in our native apps and running the TS code through that. Not as interesting, but lets us import any random npm package (which is both good and bad).
- janpaul123 5y agoHah, cool! I agree that for your use case V8 makes more sense :)
- watermelon0 5y agoAre you using V8 heap snapshots to speed up startup time by any chance? IME this can greatly improve cold starts.
- cromwellian 5y agoI wonder if you've looked at AssemblyScript https://www.assemblyscript.org/ https://www.assemblyscript.org/
- janpaul123 5y agoYep, but it doesn't really solve any of the problems that I mention in the article, unfortunately.
- dudus 5y agoI thought WASM was considered slower than JavaScript only for the use cases where heavy usage of DOM is necessary. Since that's not available through WASM it needs to make a call to a JS function to perform the DOM manipulation and that incurs extra overhead. I think these cases will just never be good for WASM. DOM manipulation heavy Apps (which is a good chunk of JS code out there) may continue to be in JS and only the CPU intensive tasks would be computed in WASM. Isn't that the best practice for WASM?
- meetups323 5y agoBrowsers are good enough that if you're careful about it you can just use a WebWorker and write standard JS, no need to worry with the build/compilation/compatibility headaches adding a whole new language to your ecosystem entails. Nice part with this is you can even have a fallback routine that runs the WebWorker code in the main thread to support really old browsers. I wouldn't recommend dropping to WASM until you've ironed out the ideal data structures/algorithms to use, and have spent some time in the devtools profiler. Hacking on ideas in JS is (IME) much faster than doing the same in a compiled language, and the built in profiling in browsers works far better for JS than any WASM (again IME). Only after you have an optimized algorithm, data structure, and implementation, and still find perf isn't where you want it would I recommend rewriting in a compiled language -- even then I'd wager having the optimized reference implementation around will make this much easier and faster than if you tried to start from 0 in the compiled language. Bonus points if you can reuse tests and/or have a debug mode that runs both implementations in parallel and throws on any differences in output.
- OtomotO 5y agoBut I prefer to write other languages, so a compilation target is perfect. So it's either wasm or something like scala.js or fengari.io (lua on the web) (Worry not, I am paid to write JS and TS these days... But my rate increased due to the pain it causes ;))
- dgb23 5y agoThis issue is being worked on: https://hacks.mozilla.org/2019/08/webassembly-interface-types/ https://hacks.mozilla.org/2019/08/webassembly-interface-type...
- rob74 5y ago> Toolchain integration can be daunting. You need to set up Rust in development builds, production builds, local testing, continuous integration, and so on. Impressive that someone familiar with the usual JS development toolchains can write this and keep a straight face...
- deleted 5y ago[deleted]
- janpaul123 5y agoYou didn't see my face when I wrote that ;)
- deleted 5y ago[deleted]
- emteycz 5y agoWhat's so hard about JS toolchain? yarn create next-app --typescript ... and I am done
- girvo 5y agoFor me it’s the same yarn create react-app my-app --template typescript
- edgyquant 5y agoNow include installing node/npm initially and setting that app up for CI/CD
- emteycz 5y agoSure: FROM node:16 Done (including dev env with VSCode dev containers). And if you created a Next app as I suggested, your CI config is super-easy - you need to place 3 commands into your YAML and that's it: yarn lint yarn build yarn start
- 5y ago
- simjnd 5y agoNot sure if you're familiar with or heard about AssemblyScript [1]. It sounds like the goal you are trying to achieve, but with a saner approach: create a TypeScript-like language that compiles to WebAssembly. This means it gives you native access to SIMD, and eventually even threads! All while avoiding the overhead of learning a new language (well there are still some gotchas since it is actually a new language, but very close). No need to think about whether you need to optimize X function or manually manage memory somewhere, and still get the awesome performance of Wasm. [1]: https://www.assemblyscript.org/ https://www.assemblyscript.org/
- janpaul123 5y agoYep, but it doesn't really solve any of the problems that I mention in the article, unfortunately. The Typescript familiarity part is nice, but it doesn't really work well with existing code; you still have to set up Wasm-JS communication; it doesn't work with existing ArrayBuffers / multiple memories; it's garbage collected if you want to use more complex objects; etc. In many ways it's better to use Rust (which looks a bit like Typescript) than AssemblyScript..
- deleted 5y ago[deleted]
- wrnr 5y agoI've tried AS for a while but then just learned Rust. The wasm ecosystem is a lot more mature on Rust (probably the best of all languages) so I'm glad i'v learned it. AS syntax looks like TS, but under the hood it's another language with a lot of open problems like testing, sending data back and forth between wasm and js. AS supports operator overloading, but only for for values of the same time, like Vector * Vector, but not BiVector * Vector, and Rust does.
- orta 5y agoYeah, AssemblyScript is a key WASM language variant - I made a list of some of the other TypeScript-like implementations last year. Static TypeScript and TypeScriptCompiler do something similar in order to run inside web pages. At a quick glance, this proposal might get some ideas from the thinking done on BorrowScript https://github.com/orta/awesome-typescript-derived-languages#awesome-typescript-derived-languages https://github.com/orta/awesome-typescript-derived-languages...
- freeqaz 5y agoDumb question: Do you mean "Fast" as in speed of execution, or "Fast" as in compile times? (Or maybe you mean both? I haven't used Rust.) Might be worth clarifying because I immediately got excited about a faster TypeScript compiler!
- johnny22 5y agoRust isn't known for quick compile time.
- janpaul123 5y agoYeah I meant speed of execution. Sorry for the letdown! But https://github.com/swc-project/swc https://github.com/swc-project/swc is a very fast Typescript compiler written in Rust :)
- huydotnet 5y agoSorry for nitpick, but in the TypeScript example, should it be for (const vec of vecs) Instead of: for (const vec in vecs)
- janpaul123 5y agoTnx; fixing!
- quickthrower2 5y agoThat syntax of JS bugs me the most. The “in” keyword could be scrapped and have a .keys() on the object but alas back-compat. Mnemonic: of = objects fail, in = index names Yeah yeah objects can be arrays and iterable but you know what I mean. Best I could do! If you dont like just use the “in” mnemonic and Sherlock Holmes.
- pjmlp 5y agoNice work! Note that Microsoft actually has a compiler from a Typescript subset into C++, as part of the MakeCode IoT education project.
- janpaul123 5y agoAh yes; I did see that but forgot to mention it: https://makecode.com/language https://makecode.com/language
- omegalulw 5y agoShould be called transpiler instead?
- pjmlp 5y agoThat is a fashion word from the JS community. A compiler is a compiler, regardless of the target language. Are you aware originally C compilers did not generate machine code directly, rather Assembly source and then called the Assembler on it. C++ and Objective-C initially generated C. Eiffel to this day generates either C and bytecode. Nim and Haxe are another examples, and so forth.
- triyambakam 5y ago> Various articles have been written, comparing the performance of Javascript versus WebAssembly ("Wasm") in the browser. Nit pick: Every word in this sentence in the article is a link. This way of linking multiple related resources is so hard to keep track of and navigate. I need to hover over or click each word to discover the resource.
- virchau13 5y agoIt's not that bad in my opinion? I just middle click to open each link in a new tab. I can read the new tab, close it, leave it open, whatever. The links I've already clicked will turn purple, meaning I can easily see which links have been visited already. Is there something I'm missing?
- tomjakubowski 5y agoThe nit pick I think is that because every word is a separate link and the link style doesn't show the extent of the underline until you hover over it, it isn't easy to see without hovering on every word individually that it isn't just one link with longer text.
- rattray 5y agoI've never written (or even read) JS code with ArrayBuffers. I wonder how much mileage you might get with better education and/or libs for using them? And perhaps a highly opinionated linter for things like optimal for-loop patterns?
- chadcmulligan 5y agoYears ago I wanted to use Typescript for native development, then Swift came along, try Swift if thats what you're after.
- janpaul123 5y agoIf Apple would truly commit to making Swift applications run cross-platform (Windows, Mac, Linux, Android, iPhone, Web/wasm) in a performant way, they could completely take over and become the next stack for performance-intensive applications.
- Cthulhu_ 5y agoI like(d?) Swift and I do believe there were some projects to make it more usable for server-side applications, but it still seems to be limited to Apple applications. But it has the potential to go beyond, it just needs marketing. It worked for Go (from a Google-internal language to replace C++ to a widely used popular language), Rust (from a Mozilla internal project to solve their issues to a widely used and popular language), even Typescript (as a replacement for flowtype, coffeescript (?), atscript (that was a thing) and some others, again developed, sponsored and promoted by a big organization (Microsoft) after being necessary for their large scale web applications (Office). anyway, Swift uses LLVM to compile so OS-specific libraries aside, cross compilation shouldn't be a problem - even to WASM.
- scns 5y agoThey would need to catch up to Kotlin Native [0]. They fixed the differences to the JVM memory model in 1.6.20 [1]. [0] https://kotlinlang.org/lp/mobile/ https://kotlinlang.org/lp/mobile/ [1] https://kotlinlang.org/docs/whatsnew1620.html#concurrent-implementation-for-the-sweep-phase-in-new-memory-manager https://kotlinlang.org/docs/whatsnew1620.html#concurrent-imp...
- xixixao 5y agoRelated but more high level, I think we should have language sets instead of single languages with simpler interop and shared infra: https://gist.github.com/xixixao/8e363dbd3663b6729cd5b6d74dbbf9d4 https://gist.github.com/xixixao/8e363dbd3663b6729cd5b6d74dbb...
- simpleguitar 5y agoI love this concept!
- jeofken 5y agoLove this
- janpaul123 5y agoYes!!
- woojoo666 5y agoCool idea! It seems like you are trying to extend the whole "optional types" idea of Typescript further to add optional low level programming. One thing that came to mind. One of the major reasons why Typescript's optional/gradual typing system works so well is because the the type system and the runtime logic are mostly orthogonal. All the types that are used and declared in typescript, are stripped away by the compiler and don't affect the javascript logic. This means that if I import some other team's typescript code, I can freely write javascript on top without worrying. Low-level operations like manual memory management are not like that. They are part of the runtime, and _do_ affect the business logic. It's a leaky abstraction. So if I import some other team's Rust code and I try to write RustScript on top, I might have to worry about some special edge cases where the argument I pass in isn't properly garbage collected or what not.
- creativemonkeys 5y agoAs far as I can tell, D already implements this idea. The Rust/RustGC duality is very similar to D's approach, which has manual memory management and an optional GC [1], and DMDScript [2] could be the scripting language. [1] https://dlang.org/spec/garbage.html https://dlang.org/spec/garbage.html [2] https://github.com/DigitalMars/DMDScript https://github.com/DigitalMars/DMDScript
- nightski 5y agoThe older I get the less I care about "expressive power" and "speed of development". I don't view these things as an advantage most of the time, actually often the opposite. The price is always paid later on, and it is high.
- Ericson2314 5y agoI absolute care about "speed of maintainence". There is no reason "speed of development" need be construed to mean banging out the initial garbage prototype, and only that. We can be tortoises rather than hares and still care about average speed long term.
- deleted 5y ago[deleted]
- deleted 5y ago[deleted]
- nightski 5y agoI think we are more in agreement than your post suggests! I was mostly against the notion in the original post that dynamic/interpreted languages are the pinnacle of development speed (which is why I thought the article was referring to "speed of development" as slinging code). In reality, I care about the same average speed long term as you do and for that I'd take a statically typed language any day.
- Ericson2314 5y agoGlad to hear it!! :) I agree untyped for more developer speed is a bad joke.
- MathCodeLove 5y agoThose things are very important for getting a product off the ground. You may have a jaded view from fixing what someone else wrote, but there's a non-trivial chance that in many of those instances no such app would exist to fix had it not been for the ability to rapidly develop and launch said app. You also speak as if speed of development/expressivity are in direct opposition of maintainability and structure, which just isn't true.
- apatheticonion 5y agoSounds like BorrowScript; which is TypeScript syntax, a Rust borrow checker, and Go-like coroutines. It's designed for wasm and web api targets. (not compatible with TypeScript though) https://github.com/alshdavid/BorrowScript https://github.com/alshdavid/BorrowScript
- deleted 5y ago[deleted]
- lowbloodsugar 5y agoSorry, are we saying that Typescript is as fast as Rust in general, or when Rust is compiled to WASM, or what? I am confused and don't know enough about the problem to figure it out myself.
- langsoul-com 5y agoIn the end, wouldn't it all be rendered into JavaScript, thus there's a hard performance limit?
- Cthulhu_ 5y agoKind of; if it's compiled to WASM instead of JS it could be faster. And some years ago there was asm.js, a sub (or super?) set of JS that removed a lot of the dynamic nature of JS, allowing an interpreter to skip a lot of checks and make more assumptions, and therefore make it run a lot faster. Probably made obsolete due to WASM though.
- chx 5y agoThese two lines are worth looking at because they showcase why I am not really fond of Rust as much as people who would rewrite everything from the Linux kernel to the IMF in Rust if they could (which in itself is seriously off putting if my tone didn't tell you that): type Vec2 = { x: number, y: number }; function avgLen(vecs: Vec2[]): number versus: struct Vec2 { x: f64, y: f64 } fn avg_len(vecs: &[Vec2]) -> f64 { There has been more than two decades between the release of Perl and Rust and you haven't learnt anything? Wow. Listen: code written in languages heavy with sigils and shorthand are hard to understand, read and most importantly: maintain. This adds a mental load which is -- as clearly visible from above -- is totally unnecessary. I have no idea why would anyone in 2010, several years after the famous "memory is the new disk, disk is the new tape" do this to save on characters in the source code. The teletype is a distant memory. Thanks for letting me vent a little.
- mhaberl 5y agoI don't mind venting at all, but don't agree. > This adds a mental load which is -- as clearly visible from above -- is totally unnecessary For numbers in Typescript you basically have a number type (there is also that story about BigInt, but...) - this is as good as Rusts f64 In Rust you have: u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64 Sure, you could have some kind of alias number = f64 - but why? Rust and Typescript have different uses. For the web, number (f64) is good enough. If you do system programming then you need some options.
- sondr3 5y agoIf you view Rust as a high-level language, then yes, there's probably a slight overload of sigils, operators and characters. However, Rust was not outright designed to replace languages like Python, Java or JavaScript but systems level languages like C++. Given that is has no runtime and a fair few compile time guarantees, as a programmer you need to tell the compiler explicitly what something is and how it is owned. If you come from a language with a runtime and start using Rust as if it is another high-level language you will hit the borrow checker wall at some point. You could also write the function above as `fn avg_len(vecs: Vec<Vec2>) -> f64 {`, and I personally would write the TS one as `const avgLen = (vecs: Array<Vec2>): number`. However, there's a difference between the ownership of a `Vec` and `&[]`, something a language like TS doesn't need to worry about but Rust does.
- monssoen 5y agoA feature that Haxe offers could help here. Abstracts are compile time only types of which the implementation is fully inlined. Meaning we could define an abstract over ArrayBuffer which has an iterator of abstracts over Float64Array which define the x and y property getters. Once compiled the code will look very similar to the example given. It's one of the things I miss the most in TypeScript, coming from haxe. https://haxe.org/manual/types-abstract.html https://haxe.org/manual/types-abstract.html https://code.haxe.org/category/abstract-types/color.html https://code.haxe.org/category/abstract-types/color.html