Sometimes you'll need to make a change in the codebase that can't automatically be propagated to dependent functions, for example: if you alter a data constructor for a type or change the arguments to a function. Unison can programmatically guide you through the edits that you need to make in these cases.
Note, your code which calls the old versions of a function will not be broken (it'll still run as originally implemented) but it might refer to the old version by hash reference, which is how you end up with cryptic terms that look like this:
exclaim : Text
exclaim =
use Text ++
#7i0ceb20mm 4 ++ "!!"
This document walks through a workflow for making changes to your codebase and resolving the resulting conflicts in a way that helps to preserve the human readable names for your functions. 🤖
Walk through an example
Let's say we have a simple type and a function which makes use of that type in our codebase:
unique type Box = Box Nat
Box.toText : Box -> Text
Box.toText box = match box with
Box.Box nat -> Nat.toText nat
Later, we may realize its data constructor should be changed. The first thing we should do is branch
from our working branch so we have a nice environment to make our changes in.
myProject/main> branch updateMyType
In our new branch, let's edit the type:
myProject/updateMyType>
myProject/updateMyType> edit Box
We've changed the data constructor from taking a value of type Nat
to Int
.
unique type Box = Box Int
After making the change to the type we'll update
the codebase, and see if we have any functions or types to adjust given our change with the todo
command. Remember, we're making these updates in a branch.
myProject/updateMyType> update
myProject/updateMyType> todo
🚧
The namespace has 1 transitive dependent(s) left to upgrade.
Your edit frontier is the dependents of these definitions:
unique type Box
I recommend working on them in the following order:
1. toText : Box -> Text
We only have one todo item here, but in a more complicated codebase the suggested edit order from the UCM can be extremely helpful in prompting you to work from the "edges" of your codebase, working up the function call chain.
In this case, it looks like the toText
function will need to be changed to point to the updated type in this branch and a few names and functions should be changed to handle the Int
. Because we've kept around a copy of the old data type the old branch, the UCM renders it with the hash of its project prefixed before the name of the type (this is a temporary stop-gap) instead of an entirely cryptic hash.
-- old code
Box.toText : <projectsHash>.Box -> Text
Box.toText = cases <projectsHash>.Box.Box nat -> Nat.toText nat
-- new version ✨
Box.toText : Box -> Text
Box.toText = cases Box.Box int -> Int.toText int
When you're satisfied with the changes in your forked namespace and there are no more todo
items, you can merge
the new branch into the old branch to fully replace the old implementation. We can clean up our branch structure with the delete.branch
command
myProject/updateType> merge /updateType /main
myProject/updateType> switch /main
myProject/main> delete.branch /updateType
There you have it! You've updated your codebase with the help of the UCM! 🌻