Magic refactoring with Unison: much more than a new programming language
By: Gonzalo Ruiz De Villa
Tags: Clean Code, Programming Languages, Refactoring, Functional Programming, Distributed Computing

Unison language

I recognise it, I am obsessed with refactoring. When I program, I keep changing the names of the variables, methods or classes again and again. Or, for example, there are no functions to which the arguments do not change without stopping, until finally I am slightly satisfied. I always have the feeling that something more can and should be done.

That is why I was surprised by the refactoring capacity of this new programming language called Unison. As I said, I rename things obsessively, but this has a cost. For example, when renaming a function, you have to modify all references, or, what is the same, you have to modify all the files where there are functions or methods that invoke the renamed function. Let’s say that in the project there are a thousand files with calls to that function: this implies that after the renamed, in the code commit, of the 1001 files it will contain, only one will have a relevant change, while the changes in the rest of the files only generate noise. But… could it be otherwise?

And, of course, the answer is yes (otherwise this article would not make sense, right? 🤷🏼‍♀️).

In Unison, to change the name of a function, we use the move command:

.> move.term base.List.foldl base.List.foldLeft

If we now review the implementation of a function that uses the old foldl, now renamed as foldLeft, we see that Unison’s magic has already done its work:

.> view base.List.reversebase.List.reverse : [a] -> [a]

base.List.reverse as =

use base.List +:

base.List.foldLeft (acc a -> a +: acc) [] as

When you rename a function with Unison, all references are instantly corrected. But in order to do so, what Unison is not going to do is to mutate texts on your behalf, updating thousands of files, generating a gigantic diff and breaking the libraries of users who expect the function to continue using the old name. What a madness, right?

Unison is a typed language largely influenced by Haskell, Erlang and a research language called Frank. There is a very simple and basic idea on which Unison relies: to identify definitions not by name but by content. That is, if we define a factorial function as the product of the natural numbers between 1 and a given number n, it will not matter if the name that we give the function is “factorial”, “lairotcaf” or something else. Similarly, it is completely unimportant if the parameter is called “n”, “z” or any other name: the function does not change in its essence.

What Unison does is the following: first, Unison calculates the hash of the implementation. Second, instead of storing text files, what you save in the code base is the abstract syntax tree (AST) of the function, where references to other functions are made using the corresponding hashes. In this way, a management of the base code is achieved that allows, among other things, the following:

not having to recompile anything

trivial renaming

cache test results

eliminate dependency conflicts

persistent typing and simple storage

The Unison base code manager is the piece that makes all these things possible by storing the AST and becoming the only source of truth and not relying on textual representations found in text files. The base code manager has some very interesting properties:

append-only: definitions are never modified or deleted, only new ones are added.

as a result of the above, it can be versioned and synchronised with Git or similar tools without generating conflicts.

as it can only be added, many types of information can be cached without worrying about the expiration of the cache.

the names are stored separately from the definitions, so renaming is very fast and 100% accurate. In addition, aliases can be added easily.

I believe this comment by the creators of the language is very clarifying:

Unison provides some files, the scratch files, where you can explore the codebase definitions and make and test the changes before saving them in the codebase manager.