The process of applying a potentially breaking change to your code need not be stressful! Here's how Unison handles that process in a few common cases:
Updating your own code
Use the edit command to bring code into your scratch file and update to apply those changes.
If your code change cannot automatically be propagated to its dependents, for example, if you alter a data constructor for a type or change the arguments to a function, Unison will programmatically guide you through the edits.
Example walk-through
Let's say we have a simple type and a few functions that use it in our codebase:
type Box = Box Nat
Box.toText : Box -> Text
Box.toText box = match box with
Box.Box nat -> Nat.toText nat
Box.print : Box -> {IO,Exception} ()
Box.print box =
Box.toText box |> printLinescratch/main> updateWe add it to the codebase, but later, we realize its data constructor should be changed.
scratch/main> edit Boxtype Box = Box IntWe've changed the type from taking a value of type Nat to Int. Upon saving the file and running update again, the UCM will open the impacted terms in your editor and create a -update branch for resolving the conflicts.
scratch/main> update
Some definitions don't typecheck with your changes. I've updated the file
scratch.u with the definitions that need fixing. Once the file is
compiling, try `update` again.
I've also switched you to a new branch update-main for this work. On `update`, it will be merged
back into main.
scratch/update-main>All impacted terms, including indirect dependents, will be opened in your scratch file. In our example, the function Box.print is opened in the editor even though the change we need to make is in Box.toText. This is because resolving larger, more complicated updates can involve propagating changes to many terms, all the way up the function call chain.
type Box = Box Int
-- The definitions below no longer typecheck with the changes above.
-- Please fix the errors and try `update` again.
Box.print : Box ->{IO, Exception} ()
Box.print box = Box.toText box |> printLine
Box.toText : Box -> Text
Box.toText = cases Box nat -> Nat.toText natResolve the compilation errors with the help of the UCM.
The 1st argument to `Nat.toText`
has type: Int
but I expected: Nat
5 | Box.toText = cases Box nat -> Nat.toText nat
6 |
7 | type Box = Box IntBox.toText : Box -> Text
Box.toText = cases Box int -> Int.toText intWhen the scratch file compiles, you'll see a message describing the change set.
scratch/update-main>
Loading changes detected in scratch.u.
~ type Box
~ Box.print : Box ->{IO, Exception} ()
~ Box.toText : Box -> Text
+ (added), ~ (modified), - (deleted)
Run `update` to apply these changes to your codebase.Run update again to apply the change and the UCM will finish the update by deleting the temporary branch.
Deleting terms in change sets
When you're resolving an update, you might realize that some definitions are no longer needed. To handle this, UCM has a specific workflow for removing terms as part of a change set.
Here's how it works:
- During the update process, UCM creates a temporary update branch and opens the affected terms in a scratch file.
- If you remove a term from that file, UCM interprets it as a deletion and removes it from the codebase in the resulting change set.
From our preceding update example, say we wanted to remove Box.print while resolving the type error.
type Box = Box Int
-- The definitions below no longer typecheck with the changes above.
-- Please fix the errors and try `update` again.
Box.toText : Box -> Text
Box.toText = cases Box int -> Int.toText int
Box.print : Box -> {IO,Exception} ()
Box.print box =
Box.toText box |> printLineRemoving it from the file causes Box.print to be listed as a deletion. It won't be present in the codebase after resolving the update.
Loading changes detected in ~/Unison/website3/website/testTour.u.
~ type Box
~ Box.toText : Box -> Text
- Box.print : Box ->{IO, Exception} ()
+ (added), ~ (modified), - (deleted)
Run `update` to apply these changes to your codebase.How to update a library dependency
Upgrading a library is very similar to the regular process of updating Unison code. It involves one additional simple command. The following workflow uses Unison's standard library, base, as an example.
Upgrade workflow:
lib.installthe latest version of the dependency into your codebase so that it is a sibling of your current library version.myProject/main> lib.install @unison/base
- Run the
upgradecommand, indicating which library you'd like to upgrade myProject/main> upgrade unison_base_1_0_0 unison_base_2_0_0
- Run the
- The UCM will create a new branch for the upgrade to take place in, so if something goes wrong and you want to back out of your changes, you can easily switch back to the branch you were on before the upgrade.
- If there are conflicts to resolve, the UCM will open up the affected terms in your editor. Resolve the conflicts and enter
updateagain once the file typechecks. - Run
upgrade.committo merge the temporary branch created by theupgradecommand back into its parent branch. It will delete the temporary branch.
Updates from merging
Merging two branches together can also prompt an update workflow. The merge commands below are all valid ways to merge changes from one branch to another.
scratch/main> merge /featureBranch
scratch/main> merge /featureBranch /featureBranch2
scratch/main> merge /@contributor/featureBranchIf a term has been updated on the parent branch and the child branch being merged into it the UCM will do two things:
- It will create a branch with the name
merge-child-into-parentfor the merge to take place in. - It will open up the impacted terms in a scratch file. Conflicting terms are prefixed by their branch name in a comment for resolution.
-- tmp/main
term1 : Nat
term1 =
1 + 2 + 4000
-- tmp/updateTerm1
term1 : Nat
term1 =
1 + 2 + 5000Delete the term that is no longer up-to-date or combine the changes into one term, then run update to apply the change to the merge resolution branch.
tmp/merge-updateTerm1-into-main> updateOnce the codebase changes are resolved, run merge.commit to commit the changes into the parent branch. merge.commit will also delete the merge resolution branch.
tmp/merge-updateTerm1-into-main> merge.commit