Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

This hits close to home. TypeScript is also my language of choice for 90% of the software I write. I agree with the author that TypeScript is very close to the perfect level of abstraction, and I haven't seen another language with a type system that's nearly as enjoyable to use. Of course, TS (any by extension JS) obviously has its issues/complications. Bun solves a lot of the runtime-related issues/annoyances though.

For the other 10% software that is performance-sensitive or where I need to ship some binary, I haven't found a language that I'm "happy" with. Just like the author talks about, I basically bounce between Go and Rust depending on what it is. Go is too simple almost to a fault (give me type unions please). Rust is too expressive; I find myself debugging my knowledge of Rust rather than the program (also I think metaprogramming/macros are a mistake).

I think there's space in the programming language world for a slightly higher level Go-like language with more expressiveness.



I'm surprised ocaml doesn't have more market share here. Native, fast, robust type system, GC, less special syntax than rust, less obtuse than Haskell.


No libraries.

All the niche languages have a chicken and egg problem.

Only way around that is to be able to piggy back on C or JavaScript or Java.


Yeah, though when I say that I mean more like I'm surprised the ecosystem itself hasn't matured more. Rust and Go have both built solid ecosystems up from scratch, but it's a shame that OCaml hasn't since it's a nice middle ground between the two.


I really considered one of the BuckleScript/js_of_ocaml languages for my current frontend project, but went with Typescript for basically conservatism/conventionalism reasons. I was already taking some arguably unnecessary technical risks on that project and wasn't sure I could afford more. I agree that I really wish OCaml or something close to it was mainstream.


Sounds like a lot of people here are promoting Swift, which IIUC has some roots in OCaml. I have a mac now; maybe I'll finally give it a try.


Until recently it did not have multithreading


TypeScript is good as a language. You can't generate static binaries out of it (except Docker images) and that itself is a deal breaker.


You can’t? I haven’t used it, but what’s wrong with “deno compile?”

https://docs.deno.com/runtime/reference/cli/compile/


You are the fifth person who told me that this can be done but have never used it.

If this compile approach is so awesome why don't devs use it?


Sounds like electron but for cli


I does indeed bundle it's runtime into the executable, but this is not dissimilar from Go bundling it's gc into every executable.

Granted a compiled hello world Go program will be <2mb while a similar compiled Deno program will be more like 70. I imagine many usecases may not have a storage constraint though, and in that case why not?


My problem is running post install step of 100+ npm packages on my machine. Any of those could be malicious.

https://www.sentinelone.com/blog/unseen-threats-in-software-...


Yeah, that's definitely a huge drawback. Bun lets you get pretty close though with the `--compile` flag: https://bun.sh/docs/bundler

Too bad the binaries are 60MB at a minimum :(


I'd love to see someone develop a compiler for TypeScript that got, say, Ocaml-like performance. There's a bunch of reasons why that'd be tough though - you'd probably want a language very-similar-to-but-not-quite-like-TypeScript.


This is probably the closest thing to that: https://www.assemblyscript.org/


The documentation is really incomplete. It's not at all clear how similar to TypeScript this is...does this support structural subtyping? Type manipulation? Iterators/generators? Async?


There used to be reasonml but I'm not sure how active it is these days.


ReasonML is just OCaml with a different syntax. It sort of very loosely resembles JS syntactically in that it's got curly braces, but that's about as far as the similarities go - about as much as Rust resembles C. The type system and language are very different from TypeScript or JS, and much more rigid.


its called C#


C# is a lovely language but it's not that similar to TypeScript. The big distinction (for me) is that it's nominally-typed and much more rigid. Unless things have changed a lot since I last touched it (admittedly a while ago):

You can't synthesize ad hoc union types (https://www.typescriptlang.org/docs/handbook/2/everyday-type...)

There's no notion of literal types (https://www.typescriptlang.org/docs/handbook/2/everyday-type...)

There's no type narrowing (which gets you a kind of sum type) (https://www.typescriptlang.org/docs/handbook/2/narrowing.htm...)

Most of the type maniuplation features (keyof, indexed access types, conditional types, template literal types...) are missing (https://www.typescriptlang.org/docs/handbook/2/types-from-ty...)


Close but not quite.


I mean you can, they're just inappropriately large.


I recently came to a production Typescript codebase and it took minutes to compile. Strangely, it could not behave correctly without a linter rule `no-floating-promises` but the linter also took minutes to lint the codebase. It was an astounding exercise in patience. Faster linters like oxlint exist but they don't have a notion of cross-file types so `no-floating-promises` is impossible on them.

The worst part is that `no-floating-promises` is strange. Without it, Knex (some ORM toolkit in this codebase) can crash (segfault equivalent) the entire runtime on a codebase that compiles. With it, Knex's query builders will fail the lint.

It was confusing. The type system was sophisticated enough that I could generate a CamelCaseToSnakeCase<T> type but somehow too weak to ensure object borrow semantics. Programmers on the codebase would frequently forget to use `await` on something causing a later hidden crash until I added the `no-floating-promises` lint, at which point they had to suppress it on all their query builders.

One could argue that they should just have been writing SQL queries and I did, but it didn't take. So the entire experience was fairly nightmarish.


Promise<T> is not T and Typescript's type system will catch that in most cases with a simple type assertion (add a type to the function return; add a `satisfies` check somewhere).

If it isn't catching it often enough, without a lot more extra type assertions, you may be missing a Typescript strict flag like noImplicitAny. (In general I find that I prefer the full `"strict": true` in the compilerOptions of tsconfig.json and always write for all strict checks.)

Also if your codebase is still relying on "explicit" `any`, try `unknown`.

Also, yeah Knex doesn't look like the best ORM for a Typescript codebase. Typescript support in Knex is clearly an after thought, and the documentation admits it:

> However it is to be noted that TypeScript support is currently best-effort.


Yeah, the codebase in question was using an “insert into db but do not check result and let exceptions inform” pattern. It was probably transformed from JS at some point which is why it used Knex I must guess.

So I suppose I should have found some condition to force use of return values and then unleashed Claude code to fix up linting errors.

It’s rarely a GetX resulting in a Promise<T> that’s mismatched and more an insertX followed by an insertY on the same transaction without awaiting the first insertX that’s the problem. Makes it a heisenbug.

I wouldn’t mind it if the linter was oxlint speed but eslint was a 5 minute affair on the codebase. It’s sort of a Moravec’s Paradox of its own: web development is much more complex than the HFT strats we deployed (which could compile and sim something faster than this can lint).

I suppose I can’t blame the query builder on the tool choice but they were pretty stuck on Knex by then.

Your advice is appreciated. Thank you.


Yeah, that's about the point where I would have started mandating every insert be awaited and not just "fire and forget" (or "let the unhandled promise rejection handler sort it out"). Take the linter's advice there and do it everywhere. Would solve a lot more timing heisenbugs and race condition uncertainty, in my experience.

Also yes, eslint is quite slow on larger codebases, but getting faster.

I've more and more using `deno lint` which is similar to oxlint (also Rust based, also trying to be more or less similar to eslint with certain "presets" available) because I mostly like its typescript "required" opinions (I don't configure it much beyond its out of the box configuration). You can use `deno lint` just fine even if you aren't using deno as a runtime.

I think if I was working on a production Node app with a large codebase right now I'd consider `deno lint` or `oxide` as first pass in the inner developer loop and `eslint` configured with the same presets as a CI-mostly second pass.


You don't have to suppress it, you just write `void` in front of the floating promise.

Which is arguably the correct way to handle this situation anyway. It makes it unmistakably clear that you are intentionally throwing away a return value.


I very much enjoy reading and writing TS code. What I don't enjoy is the npm ecosystem (and accompanying mindset), and what I can't stand is trying to configure the damn thing. I've been doing this since TSC was first released, and just the other day I wasted hours trying to make a simple ts-node command line program work with file-extension-free imports and no weird disagreements between the ts-node runner and the language server used by the editor.

And then gave up in disgust.

Look, I'm no genius, not by a long shot. But I am both competent and experienced. If I can't make these things work just by messing with it and googling around, it's too damned hard.


I encountered this trying out PureScript. Looks like a good language, but I gave up after a couple of trips through npm, bower, yarn...


Fully agree. Try bun.


Or deno.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: