Hopping through Unison

At work I get to deal with codebases that use a variety of programming languages and tooling to make a bunch of stuff in the world of the web. These tools have been battle-tested and they keep getting better. Yet I have this urge to explore alternatives, because I find these type of adventures fun and often educational. And so a while ago, I discovered the programming language Unison. In this post, I'll be sharing my experience learning this new programming language.

β“˜

What is Unison?

A pure functional programming language where code gets stored in a database instead of files.

Entering the rabbit hole

I stumbled upon Unison when trying to learn more about algebraic effects in languages such as Koka and OCaml. While I enjoy getting to know languages that have the sales pitch of {WELL KNOWN LANGUAGE} but better, Unison got me curious instantly with: code stored in a database instead of a text file!

Thinking in Unison

Learning and using Unison is quite a trip. The language has no fancy type features, so the learning curve was not as steep I expected. It helps if you have experience with a functional programming language, or at least if recursion is totally your thing.

I spent some time on the Unison track in Exercism. After that I tried to write code using ucm, the program that handles your code. At first, I simply did not see the point of ucm. I mean, git solves this problem, right? Well, in my case, Lazygit and git solves this for me… most of the time. What exactly is the point of this ucm program? I write some code, add it to the codebase. Next day comes, browse the code with the ui and then use edit to load a piece of a code in my editor, eventually running update to update the code in the database. Simple enough, but how exactly does that benefit me more compared to my usual workflow? I felt that I was missing something.

I don't remember exactly how it went, but while trying to write something more elaborate(a Markdown parser), it clicked: ucm isn't about adding stuff. My usual workflow, involving an array of tools and services, where everything gets added on top of each other, does not apply to a codebase stored in a database. You get to focus on code, because code-in-a-database solves a whole host of issues: dependency conflicts, compile times, formatters, source control weirdness, and a bunch of other paper cuts that happen when you mix everything together. These issues don't pop up in a "Hello world!" project, but they are problems I've experienced. It was a mental shift for me because with ucm, I just needed a lot less things to work with, while still being able to do what I liked: programming!

The ucm kool-aid

Embracing ucm made things a lot more fun. I can quickly fuzzy-find code and documentation. Using watch expressions I can have a ucm act as a REPL. Quickly write a test with a test expression and see ucm evaluate it. It feels effortless, partly because I have don't have to juggle 3 different CLI tools to achieve the same workflow.

The developer experience improves not by adding features, but reducing unexpected behavior.

No tool is perfect, and the developer experience around ucm has paper cuts as well. But during my journey, the Unison team have been swift in trying to reduce unexpected behavior. So far, I haven't found any dealbreakers when dealing with ucm, though there are some language features I sorely miss:

  • FFI
  • Some way that enables me to not manually write JSON decoders for everything.

Abilities

Earlier I mentioned that Unison has no fancy type features, well that wasn't entirely correct. It has Abilities. They are also known as algebraic effects.

β“˜

What are algebraic effects?

I'm not the person you should be asking this, but as I understand them, they are a way to track effects in your type system. They are a general abstraction, that can be used to model things like try and catch or async and await. Unison documentation has a wonderful introduction to them.

Learning this concept was difficult, mostly because I was so used to thinking in terms of "unwrapping the value". Things like Promise<T> or Option<T> are everywhere and useful. But in Unison you write these types of effects in a direct style. I started to appreciate this style quite a lot when learning about abilities. As an example, in JavaScript you might find some code that reads like this:

// The functions *needs* to be marked as async.
async function main() {
 const resp = await fetchData({...});
 const data = validateData(resp);
 const result = await writeToDatabase(data);
}

The above example is a lot easier to read thanks to async/await syntax sugar, but in Unison it's all done with abilities:

main = do
  resp = Htpp.run do fetchData data
  data = validateData resp
  result = write.tx db key data

When you start chaining a lot of functions together, like adding better error handling, improving data validation, further processing of the write result, that's when abilities really start to shine. I personally find managing (side-)effects and maintaining code readability is a lot easier this way, because it avoids a lot of complex nesting while maintaining type safety and the ability to reason about code.

So learning about the concept and using abilities was a lot easier than expected. However, I haven't been able to write my own abilities yet: there aren't many beginner-friendly examples of them, and I haven't had a real need to write my own handler for an effect.

Unison Cloud

I tried this out of curiosity as well, and also 'cause I'm so done with modern Cloud tooling. Don't get me wrong, I get why sometimes it saves a lot of time, but I still don't like how certain solutions just feel like a bunch of error-prone layers on top of layers on top of layers, oh also now with AI, whatever that entails.

Ahem, sorry I let myself go there. Their Cloud offering is a nice experience in short. No builds, no serialization to database, fast deploys, all thanks to the code-in-a-database feature. However, since the ecosystem is tiny, things get difficult when I want to do more elaborate things without reinventing the whole wheel. I do hope this situation gets better as time goes on.

So what now?

Just using Unison really, there aren't many abstractions in the language so it is a quick takeoff. More importantly, when learning any other (programming) language, trying things out with it is one of the best ways to learn and grow for me. I'm enjoying my time with Unison a lot, more so than any other language I came in contact with since I printed out Hello, World! with grand amazement in a Python REPL, so I'll try to keep making things.

πŸ””

Update: 26 April 2025

This post had been sitting for a few months now in my draft folder. Since then, I've been enjoying Unison even more. It's a great way to get a new perspective on what a different software development workflow could look like.