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.
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.
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.