Nov 26, 10:06 AM

I released my first full-stack Gleam app at work. Like most apps (especially internal ones), it’s just a thin, unremarkable layer of CRUD over a database. But working in Gleam has been an undeniably positive experience. My recent work with this type of development has been mainly with a Rust/Elm combination, so this will largely be a comparison with those languages.

Gleam and Rust

I am a huge fan of Rust. In general, I think its “drawbacks” (or at least what its detractors deride) are worth the combination of { type, memory, concurrency } safety and performance it offers. Gleam feels a lot like it provides a simplified version of a similar experience, for when you don’t necessarily need as much resource efficiency or control over asynchronous task orchestration, but would still like all the safety guarantees, along with algebraic data types, pattern matching, and parametric polymorphism. I like it.

I do miss a few things from Rust: traits are a big one. There are parametrized types, but some ad-hoc polymorphism would be great. I’d love, for example, to be able to define a logging function that would take either a string, a list of strings, or a StringTree.

It’d also be nice to have more granular visibility available in the module system. Functions can be private and available only to other functions in the same module (which also means the same source file), or public and available everywhere. It’d be nice to be able to have some type of pub(crate)-style visibility, or modules that were private, but still available to other modules in their package.

Gleam compiles down to Erlang and runs on Erlang’s virtual machine. I know zero Erlang (its syntax kind of terrifies me—and I say this as a Rust programmer), and many of the packages available to Gleam are just thin wrappers around Erlang libraries. This means that when the documentation isn’t clear, or when something seems like it’s not behaving properly, I can’t poke around under the hood like I could with most Rust libraries.

Subjects are not as nice to work with as channels, although I’m entirely open to the possibility that this is just a matter of familiarity. It seems like every time I want to use a Subject(a), I always need another Subject(Subject(a)) to send the “sender” end of the Subject back to the process where I want to use it. Maybe there’s a pattern or trick for this somewhere (I suspect it might be to just use something like the gleam/otp/actor module).

Finally, I appreciate early returns; they can keep a function’s happy path clearer. At first I missed them, but as I got better at (or at least more accustomed to) (ab)using Gleam’s use sugar”, their absence bothered me less (and is essentially gone).

It sounds like I’m complaining, but I understand trade-offs in language design. Gleam manages to be simple without seeming simplistic; it strikes an excellent balance. It also has some nice features that I’m going to miss in other languages.

Tail call optimization is definitely something I miss in Rust. Sometimes recursion really is the best way to express an operation (particularly when your chief data structure is a singly-linked list), and I like being able to take advantage of it.

The pipe operator (|>) is, in my opinion, generally superior to method chaining, particularly in conjunction with Gleam’s function capture syntax. There may be similar function capture syntax in other languages, but I’ve never used one; it’s surprisingly nice in some situations.

Finally, (and this is really more of a personal problem, I admit) because Gleam doesn’t give me the opportunity to obsess about the performance of every damned statement (“Should I copy here, or can I get away with borrowing? Oooh, maybe I can use a Cow, just in case…”), I don’t, so I just blast through things more quickly, writing more legible code.

Gleam and Elm

The discovery of Elm made me not hate the frontend. A functional1, strongly-typed, compiled language with algebraic data types is soooo much nicer to work in than a language full of WTFs and [object object]s. The lustre fontend package I have been using was largely inspired by Elm, so it was particularly easy and comfortable to pick up. Gleam uses the same model/message/update/view “Elm architecture”, and its file-and-directory-based module system makes project structure very similar. I did still miss a couple of things from Elm, though.

For such a functional-shaped language, Elm’s more OCamular syntax just seems more appropriate. It also pairs well with automatic currying, which Gleam also doesn’t have, although the function capture syntax might just make up for it.

I also find myself wishing Gleam had a function composition operator; in conjunction with function captures, this would bring even more powerful, clear concision to the language.

Gleam also scratches some itches that I had with Elm; for example, it permits variable shadowing. This may seems like a small convenience, but naming things is hard, and coming up with two good names for essentially the same thing is distracting work I’d rather not be doing.

Gleam’s pattern matching is more powerful, and lustre offers an element.none(), so one needn’t choose between returning an invisible DIV or an Option Html and then filter-mapping in the caller.

As I said, though, the vibe of Lustre was familiar, and it was quick and easy to crank out something that made sense and did the right thing.

Full Stack Gleam

I have long maintained that the idea of using the same language for both the frontend and the backend is overrated. They are different environments, and the convenience of only having to learn, or only have to use, one language is a canard.

(Seriously, if you’re working with that much of the stack, you undoubtedly also need to use at least HTML, CSS, and SQL, in addition to whatever configuration formats your stack requires—probably an un-fucking-believable amount of YAML. You’re also going to need some shell or Python or whatever for miscellaneous scripting tasks and poking around in intransigent containers or VPSes. The list goes on as an exercise for the reader.)

That being said, if you’re really into “one lang to rule them all”, then I’d argue that this is really what Node.js should have been: Instead of a rough scripting language metastasizing to the server, we have a well-designed, strongly-typed language, suitable for building complex systems, coming to roost on the front end. And there are, indeed, a couple of advantages.

The first, of course, is that I don’t have to work in Javascript. Everything else is just noise.

Having the data structres used for data transfer being the same, and defined in one place, along with the serialization and deserialization code, is kind of nice. They’ll never get out of sync.

Finally, the Gleam community is small and generous. I was graciously helped by both Louis Pilford and Hayleigh Thompson over the course of getting this thing of the ground. I’m not on board with the modern practice of using Discord instead of a forum, but the Gleam Discord is populated by nice people.


  1. Yes, sometimes Javascript gets called a functional language, too. Sure, modern JS has adopted some features that we tend to associate with functional languages, but this just brings to mind the old adage about C++ being an octopus. [return]