Why I Rewrote Nestful in Gleam
Going away from TypeScript was easy, the question was -- where to?
Yesterday I completed a partial rewrite of Nestful in Gleam, a relatively young immutable functional language that compiles to both Erlang and JavaScript.
This post tells the story of why I wanted to leave TypeScript behind, and why I chose Gleam to replace it.
A Need Rises
Nestful is a list-taking app with the promise to consolidate all of the different things competing for its users’ time. Those things (work, chores, musings, etc) would usually be stored in siloes —GitHub for personal projects, Trello for house chores, notes on life in a notes app, and so on. That’s great for prioritizing within a project, but how do you prioritize the projects themselves?
Nestful was born to solve this problem for me. The premise is simple: Nestful is a list of items, and items can be inside other items. That way I could have a list of my projects, prioritize them, and when it’s time to prioritize the project itself, I click the project’s item and inside there’s its own specific items, relating to its completion.
These were my main requirements from Nestful:
Work completely offline, with composable sync (more on that later)
Fast iteration so I could dogfood it quickly
Work on all my devices
Web it was.
I chose Vue + TypeScript because I was the most fluent with it, but it could easily have been any other client-side web technology and this post would have stayed mostly the same.
TypeScript Frustrations
As with every clean-slate project, the beginning was a breeze. I got started relatively quickly, but as I progressed and Nestful grew, problems started popping up.
A Language Within a Language
The types part of TypeScript is a language in and of itself. First you program the actual business logic, then you have to spend 10% of the time that took to wrangle types to be correct. The worst part of it?
When you’re done, you still don’t have confidence that it’s correct.
I did not delve deep enough into the typing system of TypeScript to know if it’s psychological. Maybe it is mathematically proven that once TypeScript compiles without any `any`s, it’s safe.
Maybe. I don’t care.
The way I see it, a language has to justify a learning curve. Learning to navigate Rust’s convoluted (at times) syntax is rewarded by very impressive compile time guarantees.
What does TypeScript give you that is worth the time it demands, in learning and maintenance?
Inherited Difficulties
Those problems with TypeScript are inherited and compounded when using a library. I used to spend 2-3 days when upgrading the client-side database I used at the time because for some reason, may it be TypeScript itself or the library’s author investment, the type system did not supply enough guarantees.
This is compounded when there’s a bug in a 3rd party library, or that I need to understand the inner-workings of one. I have become fairly adept in understanding other people’s code, especially in TypeScript, yet it is still more intensive of a task compared to a nicer, simple language like Gleam.
Error Handling
After Rust exposed me to the concept of errors as values, I could not go back. It was so much more ergonomic and resulted in such a better end product that I started sorely missing it. Although this is a Javascript problem first and foremost, its supersets can solve it, and some did try.
There are several attempts to make “Functional TypeScript”, but I did not like them. First, there was no nice solution to handle those returned errors, with the glaring lack of pattern matching. More than that, though — eventually, when the kitchen sink is full enough, the tools are too suffocated to be useful.
TypeScript is No Fun
Anything I do should be as enjoyable as possible, and when it comes down to it, TypeScript is just not fun.
Some things are just destined to suck, like the recent clearing up of a clogged sewer at the house. Programming Nestful is not one of them.
This is also important to its success as an application — as Nestful grew and will grow, refactors were and will be needed. Every TypeScript refactor resulted in more bugs. Gleam refactors resulted in less. More on that below.
“Compiled to WASM”
I am a relatively thorough person. As I got more frustrated with the stack I had, I began going one by one over the possible alternatives. There’s a lovely repository on GitHub tracking languages that compile to WebAssembly, so I started looking at each, one by one.
They all had one problem in common, though. Although WASM runs in the browser, it will demand a practically complete rewrite of Nestful, due to interoperability issues that will inevitably arise.
The solution is then to strictly compile to JavaScript.
I can’t remember which list I looked at at the time (maybe the one on the CoffeScript wiki?), but in the end I have narrowed it down to:
Dart
F#
OCaml
Dart was easy to pass on, even though it’s one I already knew, and on paper — the most fitting replacement — especially when adding a lot of the functional-style features I was looking for. However, it suffers from the crowded kitchen sink problem, and… Google makes it. I like using Flutter a fair bit, but I would rather split my eggs over multiple baskets, thank you very much.
F# and OCaml were pretty close. F# looked more approachable to me, but the ecosystem was fairly slow going, and I don’t paticularly enjoy working with Microsoft tools. OCaml seems to be back on track, and used in some major production codebases, but the syntax was not as familiar to me, and I got mixed signals about the rejuvenation they claim they have.
I have a soft-rule that says that in every new project I start, I must learn something new to the extend my bandwidth allows. Even though I decided not to go with either F# or OCaml this time, I’ll be happy if they turn out to be a good fit in a future project.
Then Gleam hit 1.0, which I discovered when YouTube recommended me a Primeagen video. Before, I probably skimmed over Gleam and passed for it being too young. Having it reach stability with the community momentum to compensate for being young relaxed my ecosystem worries (a little), and I took it for a spin.
Star of the Show
Gleam has quickly turned out to be an excellent choice made at the right time. It streamlined my development even though I had to write a lot of (FFI) boilerplate to get going.
As Simple as it Gets
Gleam has a simple-language philosophy. This means that the language itself is fairly small and contained (there’s no `if`!), which in turn makes it very easy to learn, and most importantly, extremely easy to understand 3rd party code.
Even with a language this young and some things that are not fully ready, I had a much better time than doing the same task with TypeScript.
Easy Refactor
As Nestful grows refactors are going to be needed. “Composable sync”, mentioned in the requirements, is an example of that.
If Nestful promises to be a place that hosts all the different things competing for my time, it better do that for things that need more than a list to be managed. Some projects do need the likes of Linear or GitHub, and I would like to be able to pull items from there and transparently display them to the user to be prioritized. They don’t need all of GitHub’s features to prioritize between fixing bug #233 and putting the car in the garage. When they actually reach fixing that bug they can click the item and Nestful will lead them right to GitHub.
The data layer is abstracted enough to achieve this, but you don’t really know what you need until you need it, especially if you avoid premature optimization like I do.
I am confident with Gleam having my back when it’s time to modify the data layer, helping me refactor correctly, even when it’s tricky. For example, Nestful has a feature that “bubbles up” items. It’s currently only used to show use deeply-nested due items in a single view (to prioritize between "bug #233 somewhere inside a project and “Put car in garage” somewhere inside house chores). Expanding this to more use cases is not trivial, and Gleam is here to help.
Fluffy and Cozy
Gleam is fun to write in. It’s a calming language if there ever was one. It’s soft to read and write (you’ll get it when you try it) and is overall a relaxing experience. That’s mostly thanks to its syntax choices.
Beyond that, it supplies the usual functional/immutable features you’d expect from a first release of such a language. Namely, to my liking, no nulls, errors as values, and pattern matching.
Enter Vleam
The cherry on the top is that I could adopt Gleam incrementally. I could add it piece by piece, replacing the TypeScript in my services, composables, and components. For that last one to be easy, I wrote Vleam.
Vleam is a set of tools (Vite plugin, Vue FFI, and LSP) that allows the usage of Gleam in Vue single-file components with full LSP and hot-reload support. Give it a try if you’re looking for a change and running Vue yourself. Easily done one component at a time.
Not Perfect Yet, But Overall Right
Gleam doesn’t have everything nailed quite yet. Some LSP features are sorely lacking (it’s rename for me), and the language could use a bit of introspection and reflection so I could avoid a neverending unwrapping of types and the chore of serialization.
Even with those rough spots, though, Gleam is still a great choice. It’s a breath of fresh air over the chore that is TypeScript, and is making big strides with every release.
Next, I’ll give it a shot on the backend.