A Brief Comparison of Modern Programming Languages - TypeScript vs Golang vs Elixir vs Rust vs F#
Date: 2024-04-05 | create | tech | programming-languages | typescript | golang | rust | elixir | fsharp |
There is no best programming language. But there are languages that are better at certain things than others.
Theo (X/t3dotgg) recently posted a comparison of programming languages across several dimensions. I found it to be a pretty high signal way to compare languages so wanted to edit, extend, and reshare it to see how far it could go.
In the rest of this post we'll be diving into comparisons of several languages across several dimensions including explanations of their rankings.
- Languages: TypeScript, Golang, Elixir, Rust, F#
- Dimensions: Concurrency, Modern Types, Pipes and Pattern Matching, Distribution Story, Runs on Windows
Want to build fullstack projects with F#? Spin up a fullstack F# app in 10 minutes with CloudSeed.
Concurrency
For Concurrency we're comparing how languages deal with multiple processes at the same time. In theory this unlocks higher performance levels and more flexible computation patterns at scale. Rankings here are based on overall usability which includes capability but also ergonomics.
TypeScript: Fail. Mainstream JS runtimes are typically single-threaded (a la Node). This often doesn't matter for web-based workloads (async gets you pretty far) but still no mature concurrency model exists. There are non-mainstream runtimes that allow this but since it's not the typical way of doing things it doesn't pass.
Golang: Pass. Go has a lot of built-in primitives for synchronizing concurrent workloads. I personally find these to be unwieldy but lower-level programmers seem to find them simple and useful. Go is very performant and provides first-class tools for these so gets a pass.
Elixir: Pass. Elixir runs on BEAM (Erlang VM) which itself is lauded as one of the best concurrent platforms in existence. Essentially everything is a process and each process is concurrent (think of it like a bunch of actors) so Elixir gets a pass.
Rust: Mixed. I don't have first-hand experience in Rust so this is coming from the experiences of others. Rust theoretically should be great at concurrency given its super speed and advanced ownership models but in practice seems to be held back by a sharded scheduling and async ecosystem leading many applications to subpar concurrency. Due to general mixed responses from Rustaceans this one gets a mixed - it feels like it should be good but just isn't in practice rn.
F#: Pass. F# runs on dotnet (same as C#) which has a simple, performant concurrency model.
- F# vs Python performance
- F# vs TypeScript performance - Sorting 1 million elements
- Is C# faster than F#?
Modern Types
I've changed this category from "Typesafety" to "Modern Types" because I believe our bar for what is passable for types in modern programming languages should be higher. Yes there are still tons of languages that don't have types (or lie about them) and I think that's a travesty - type-safe languages are simply better than dynamic ones.
Just because primitive / non-existent types are the average does not mean that they're good. We should expect our technologies to be better.
When I say "Modern Types" I mean types that are built-in, ergonomic, and useful. In general I consider a type system "modern" if it natively has:
- Deterministic / accurate type checker (Python and TS fail)
- Sum types (Golang and C# fail)
- Exhaustive pattern matching (most langs fail)
TypeScript: Mixed. TypeScript is almost there. It has a very expressive type system that allows all sorts of advanced type declarations with minimal boilerplate. The problem is that it's not native which has left gaps between the TS definition and the JS runtime. This means the type system is good but there exist edges where it lies which removes the trust that makes typed-systems good. Also TS is bolted on so requires auxiliary build steps which is annoying.
Golang: Fail. Golang has types. They are deterministic. But they are not modern. Without things like sum types we get pushed towards OO abstractions to approximate them - these are more complicated and less safe. Because we don't have sum types we typically lose exhaustive pattern matching (there are always workaround but they typically involve unenforced enum <> object coupling).
Elixir: Fail. Elixir does not have types. Yes types are supposedly coming. But IME types added later to languages rarely work as well as those built in (see: Python, Clojure).
Rust: Pass. Rust is often praised for its excellent type system. There is a learning curve but it has great expressiveness, is deterministic, and easily allows for sum types. I'd also lump ownership into its type system which is still unparallelled in other langs.
F#: Pass. If you like TypeScript or Rust types, F# has the same expressiveness but is trustworthy (it doesn't lie) and has low ceremony / complexity to write. It gives you the safety of types without unnecessary effort.
Pipes and Pattern Matching
Pipes and pattern matching are kind of proxies for ergonomics. There's probably a better way to categorize this but this was on the original comparison so we're keeping it here.
- Pipes - Allow for the reduction of intermediate vars by "piping" operations together. This further constrains mutability and makes code paths easier to read and thus is typically a good thing.
- Pattern Matching - Allows for safer, more expressive matching on possible outcomes. Coupled with Exhaustive matching, you get compiler support to ensure you cover all edge cases.
via X/SIRHAMY
TypeScript: Fail. There are libraries that allow for pipes and types of pattern matching but it is not built-in or exhaustive.
Golang: Fail. Golang leans imperative so this is largely against its design patterns.
Elixir: Pass. Elixir does support pipes and pattern matching however since it doesn't have types it doesn't do exhaustive matching.
Rust: Mixed. Rust has exhaustive pattern matching but does not have native piping (though libraries exist).
F#: Pass. Pipes and exhaustive pattern matching are built-in.
Distribution Story
For Distribution Story we're kind of thinking about three things:
- Bundling - How does a full app (and its dependencies) get bundled to run ~anywhere?
- Package Manager - How easy is it to package, distribute, and consume library code to use ~anywhere?
- Non-web environments - How well does this work ~anywhere (particularly in non-web environments)?
TypeScript: Mixed. TypeScript does have the capability to run executables on most OS and it does have a pretty good package management system but the ecosystem is relatively unstable, there's always 3 different competing systems, and runtimes will rarely beat out others for non-web workloads.
Golang: Pass. Golang has a solid and easy-to-use package manager, ability to bundle executables, and consistently good performance across non-web environments.
Elixir: Fail. Elixir is great at web stuff. It can do other things but is not the best at it. Elixir has a well-regarded package manager in Hex and does have capabilities to export binaries but it's typically not as good as other options (most docs and forum answers come with warnings). Plus Elixir does not handle compute-intensive tasks well so often falls short in non-web-based workloads.
Rust: Pass. Rust was built for this. It has a great and stable package manager, creates excellent run-anywhere executables, and thrives in non-web-based environments.
F#: Pass. F# runs dotnet which has a good package, bundling, and runtime story. It won't beat out Rust or Golang but comes out well ahead of most others.
Windows
Not worth talking about - it's basically the same as Distribution Story.
Next
There is no "best" language to choose but there are always better / worse choices for a given usecase. All of these are pretty good at web-based workloads but the differences become apparent when you consider other kinds of workloads and how robust you want your logic to be.
I personally choose F# for most web-based projects as I find it to be a great balance between usability and capability for most workloads.
Q: What is your favorite language / tech stack for building web-based projects?
If you liked this post you might also like:
Want more like this?
The best / easiest way to support my work is by subscribing for future updates and sharing with your network.