The Unison Edition
There's a new language kid on the block - and it's got huge consequences for design.
Bit of a change of pace this week.
I'll be taking a look at a revolutionary new language.
Why? What's this got to do with software design?
I think Unison will change the game of software design.
Trust me - I'm a new language skeptic.
Any time I hear of a new language I roll my eyes skywards. "Not another one..."
However, this new language, coupled with AI, will allow us to build applications we can't dream of today.
Let's get into it.
The Big Idea
The Big Idea
Unison is a typed functional language.
Each Unison definition is identified by a hash of a syntax tree.
Put another way, Unison code is content-addressed.
Example:
increment : Nat -> Nat
increment n = n + 1
The syntax tree that Unison sees is:
increment = (arg1 -> #a8s6df921a8 arg1 1)
The "+" function has a hash and Unison only sees that
Arguments are referenced by position
Most importantly...
...names are *just metadata*.
Wrap your head around that for a moment.
Every function name is metadata
Every type name is metadata
Every argument name is metadata
The function name "increment" is just a label that's mapped to a hash.
This one, simple idea unlocks some surprising and powerful benefits.
Here are a few:
First class refactoring
No builds
Easy distributed computing
Very fast tests
Sounds too good to be true?
Let’s see.
First Class Refactoring
Names are *just metadata*.
Unison knows that #arg1 is "n".
Unison knows that #a8s6df921a8 is actually Nat.+
This has huge implications for refactoring.
Imagine renaming a function...
...and the name is instantly updated everywhere.
Yup. Instantly. No work needed.
You can even create aliases for names in Unison.
Example:
There's an Optional type:
structural type Optional a = Some a | None
It's very similar to Maybe in other languages.
Other languages have Option.
Which is it? You can never remember.
In the Unison Codebase Manager, we do:
.> alias.term Optional Maybe
.> alias.term Optional Option
Now Unison doesn't care which you use.
They're all referring to the exact same type.
No more head-scratching!
This means a good name (one of the hardest things in software)...
...becomes a decision you can keep revisiting.
It gets better...
Let's say you want to do a more complex refactoring.
Maybe you add an argument.
Unison knows the hash of the old function.
It knows the new one doesn't have any callers yet.
So Unison keeps your old code working until your new code compiles!
In fact, the UCM tool steps you through the refactoring.
Yup. It gives you a list of TODOs.
All the time your codebase still works with the old code.
Once you've completed all the TODOs, Unison will use your new function.
Implications:
Your code is always in a running state
No more time wasted trawling through long obscure error messages
Predictable workflow - work through the plan Unison gives you
Imagine the evolutionary design work you can do with this tool.
Refactoring in Unison becomes a breeze and a dream.
It can't teach you refactoring, but it certainly makes it a *lot* easier.
No Builds
Wait... what? No builds?
How is that possible?
First of all, what is a build?
A build wraps up all dependencies and freezes them at a point in time.
It tends to be a slow, expensive, brittle process.
But in Unison, every function is just referenced by a hash.
And every change to a function is actually a new function, referenced by a new hash.
So every function knows it's dependencies.
Let's say you want to deploy your code.
Your machine tells a remote node "Hey, run this function called a8s6df921a8"
The node says "No idea what that hash is. Can you send it me please?"
Your machine says "Sure! Here it is: ..."
Unison will serialize the structure of the function and send it to the remote node.
Then the remote node runs it.
Of course, if it doesn't know other hashes, it'll ask your machine for them too.
So you're transferring functions over the network, not data.
That means it's quick. And it means no builds.
But wait... there's more.
Every time you change a function, Unison actually creates a new function.
So functions are immutable.
And old ones are still referenced by their hashes.
So that means dependency conflicts just go away.
Still don't get it? From the excellent Unison docs:
Dependency conflicts are, fundamentally, due to different definitions "competing" for the same names.
But why do we care if two different definitions use the same name? We shouldn't.
The limitation only arises because definitions are referenced by name.
In Unison, definitions are referenced by hash (and the names are just separately stored metadata), so dependency conflicts and the diamond dependency problem are just not a thing.
https://www.unison-lang.org/learn/the-big-idea/#no-dependency-conflicts
Practical implications
Deploys are virtually instant (< 0.1 seconds)
No builds
No dependency conflicts
No semantic version pinning (no versions!)
No CI tools
No package managers (goodbye Rubygems, NPM, yarn etc)
No Terraform
No CloudFormation
No bash scripts
No YAML configs
No Docker (ah, sweet relief)
Just write your code, run it on the Cloud and live your life.
Wow.
Easy Distributed Computing
Here's where things get really mind-blowing.
Remember that every function is identified by a content hash.
Also Unison can serialize any function and send it from one node to another.
Every dependency of one function can be sent to any other node.
If a node doesn't have any of the dependencies it needs it asks the caller...
...hey can you send me that function?
And it will.
Then the function will be cached on that node for next time.
Example:
distributed : Seq k Nat ->{Remote} Nat
distributed dseq =
use Nat + ==
dseq
|> Seq map (x -> x + 1)
|> Seq filter (x -> Nat mod x 7 == 0)
|> Seq reduce 0 (+)
Don't worry about understanding the code.
Just know that this is a distributed map reduce.
It can run on 1 node or 10000.
And it's 7 lines of Unison.
That any programmer can write.
Then it's run like so:
a4.distributed.Remote.pure.run
'(distributed (fromChunkedList 10 (List.range 0 100)))
⧨
Right 735
This runs it locally.
But there will be other runners that can distribute it in parallel across nodes in the cloud.
Want to do big data processing?
No Hadoop
No Spark
No Jar files
No Docker
No complex frameworks
Just Unison and a few lines of business logic.
Check out the blog article Spark-like datasets in Unison in Under 100 Lines for more details.
Very Fast Tests
Remember we're in a functional language.
So functions tend to not have any side effects.
You pass a function some arguments.
It returns an output.
No database access, no memory mutation.
Unison handles side effects using something called Abilities, but that's for another day.
Point is, if you have:
No side effects
Functions as hashes
Values in, values out
It has huge implications for test suites.
It means you can cache the results of any test.
Imagine a test suite that contains 100,000 tests.
You write three more unit tests and run the test suite.
The existing tests don't run.
So the test suite takes milliseconds to run.
And when it's run, the three tests you've added are cached.
What does this mean?
Huge test suites can run in fractions of a second.
No more "push tests into CI".
Just run them on your machine. It's easy. It's cheap.
This will make TDD a dream to perform.
I've spent hundreds of hours waiting for tests to pass and code to deploy.
Those days are gone with Unison.
Wrap up
There are so many more cool things about Unison that I've not touched on.
But hopefully I've whetted your appetite to learn more.
A few honourable mentions:
Abilities - a single way of doing side effects without any Monads
Codebase storage - all code is stored in a database. No more text files on disk.
No Github - Unison are creating their own code sharing and PR tools that take advantage of the language
Unison Cloud - Find out more here: https://buff.ly/3BXioSL
Typed, durable storage - Imagine if rerunning computations relied on transparent caches of old data!
Instant deploys - we touched on this, but it bears repeating - sub second deploys directly from your machine.
This language blows my mind.
If I had more time, I'd be investing all of it learning it seriously.
It’s not all sunshine and rainbows though.
Drawbacks
1. Small ecosystem - very few libraries, very little community - although what's there is super friendly and welcoming
2. Unconventional - if you've coded in Elm or Haskell a lot, it looks familiar. Otherwise, it's challenging to learn.
3. Slow - it'll take a long time to develop any kind of working software, but there are tools in the pipeline to help this. Rails it is not!
4. Closed - the language is open source but the cloud platform will not be as that's how they'll make their money.
Phew. So that was a whirlwind tour of what I think is the most promising language I've seen in the last 10 years.
A huge thanks to the whole Unison ecosystem and community.
You're doing some amazing things and I look forward to Unison cloud being in beta for everyone very soon!
What Next?
I have two options:
Book a 1 hour coaching session (normally $200, FREE for a limited time)
Have a great week!