Skip to content

Instantly share code, notes, and snippets.

@ceedubs
Created September 21, 2021 15:17
Show Gist options
  • Save ceedubs/4ed8113558b4bc2a700c6e4802893424 to your computer and use it in GitHub Desktop.
Save ceedubs/4ed8113558b4bc2a700c6e4802893424 to your computer and use it in GitHub Desktop.
Unison: error-prone update behavior with code cycles

When there is a cycle in code (ex: function f calls function g and function g calls function f), if you edit only one of the functions at a time and then run a ucm update, it won't fully propagate the change that was made.

In this example, isEven and isOdd call into each other. They are both created in a scratch file and added at the same time, and everything works as one would expect.

isEven : Nat -> {Stream Text} Boolean
isEven n =
  emit ("isEven called with " ++ (Nat.toText n))
  if n Nat.== 0 then true
  else if n Nat.== 1 then false
  else isOdd (decrement n)

isOdd : Nat -> {Stream Text} Boolean
isOdd n =
  emit ("isOdd called with " ++ (Nat.toText n))
  if n Nat.== 1 then true else (isEven (decrement n))

isOddLogs : Nat -> [Text]
isOddLogs n =
  (logs, res) = !(toListWithResult '(isOdd n))
  logs :+ ("result is " ++ (Boolean.toText res))
.testing>add
> isOddLogs 4

If you inspect their hashes, you can see that Unison gives them a matching hash prefix, because the cycle is detected and they are handled as a group.

.testing>names isOdd

.testing>names isEven

the problem

The problem comes if you decide that you want to change one of the functions. Here we just change the log message from isOdd:

isOdd : Nat -> {Stream Text} Boolean
isOdd n =
  emit ("isOdd *VERSION 2* called with " ++ (Nat.toText n))
  if (n Nat.== 1) then true else (isEven (decrement n))
.testing>update

Let's check the output now:

> isOddLogs 4

As you can see, we updated the isOdd definition to include our new log message, but it still points to an implementation of isEven that points to the old isOdd implementation with the original log message.

You can also see that the hash for isEven is the same, but the hash for isOdd has changed and is no longer prefixed by the special cycle group hash.

.testing>names isOdd

.testing>names isEven

the solution

In some ways, this is Unison doing its job, but it makes working with code cycles really error-prone. I spent half of yesterday chasing my tail because I didn't realize that this was going on.

One possible "solution" would be that if a user does a ucm edit command for a term that is part of a cycle, ucm could put the code for all of the terms within the cycle into the scratch file and output a message explaining why it did that. This won't fix every troublesome case (for example a user could just start defining a new implementation of the term without using the edit command), but it seems fairly simple and probably avoids the issue most of the time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment