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.