unison
unison copied to clipboard
[Discussion] IO Watch Expressions
This is an old Branch I'm just making a PR for to open the discussion about it since it comes up in Discord quite often.
It proves the concept of io>
watch expressions isn't too hard to build, it's more a matter of design at this point. Some questions:
- Do we re-run on every save or just when a unison dependency changed?
- Do we block UCM till it's done so we can show the result, or do we just dump the output into UCM's stdout whenever it's done even if you were typing a command?
- If it takes a long time to run (or is a server that doesn't complete) do we kill the old one when you save or do we always wait for a full run to complete?
Certain use-cases require different permutations of these, maybe we can come up with 2 or 3 different types of watch expressions that handle each purpose, but finding good names and explaining how they work isn't trivial.
Here's my use case and my thoughts, basically I'm just looking for a live-reload of local.interactive
:
- Write some HTML using the
html
lib, which gets served by alocal.interactive
server - When changing some HTML, no need to manually
run
the server again, gaining a tiny bit of dev speed.
Similiar to ~reStart
or vite dev
this helps with making an app that serves HTML.
Do we re-run on every save or just when a unison dependency changed? On save, I'd say.
Do we block UCM till it's done so we can show the result, or do we just dump the output into UCM's stdout whenever it's done even if you were typing a command?
No real preference here.
If it takes a long time to run (or is a server that doesn't complete) do we kill the old one when you save or do we always wait for a full run to complete?
I believe most dev servers kill the old one, not sure about this tho.
My thoughts:
Do we re-run on every save or just when a unison dependency changed?
On every save. If someone has io> Instant.now
in their scratch file they are going to expect the output to update even though their code doesn't.
But one concern that I would have is if people have editors that auto-save every second or something. This could get really annoying for them. But I think that it would be reasonable to release it as a new feature and see whether people complain about this (maybe they just will use run instead of IO watch expressions?)
Do we block UCM till it's done so we can show the result, or do we just dump the output into UCM's stdout whenever it's done even if you were typing a command?
No strong preference is obvious to me right now, as long as I can ctrl-c it if we opt for the former.
If it takes a long time to run (or is a server that doesn't complete) do we kill the old one when you save or do we always wait for a full run to complete?
The use-case that I have in mind is live reloading of an HTTP server, and in that case I think that we want to kill it.
@ceedubs
No strong preference is obvious to me right now, as long as I can ctrl-c it if we opt for the former.
Does this imply that UCM is blocking for it to finish? Does a ctrl-c in UCM go to UCM itself (if you run a long-running history command) or to the IO watch; and if you have many IO watches, which one?
@ChrisPenner hmm good questions. I wasn't considering the case of multiple io>
watch expressions.
One option is that ucm runs all IO watch expressions and blocks on the result, with ctrl-c interrupting all of them. But I feel like this could get annoying in the case of an HTTP server where you might not want to ctrl-c every time as you view
/edit
/etc as you work on changes.
But if it just runs all of them in the background then it will be easy to forget that they might be running. And there isn't an obvious way to stop them.
I'd imagine that the case of multiple running would be fairly rare unless we change '{IO, Exception} [test.Result]
terms to have an io>
when the are edit
ed.
So maybe we go with the blocking approach and try it on for size? If we are hesitant to commit to this feature we could put it behind some sort of flag.
I'd imagine that the case of multiple running would be fairly rare
I'd agree with this, but I can imagine that Unison might be the exception here, because people would like to run multiple services at once to do some sortof test? Just because of how easy it is to do microservices.
So maybe we go with the blocking approach and try it on for size?
For sure this though! :+1: even in Vite, Elixir-land or Scala live-reload isn't ideal, but they are comfy to use for almost everything.
Just wanted to +1 this since it came up in discord (when discussing dev live-reloading for web apps)
edit: I would probably use this to do io> deployDev
where dev is an actual non-local environment, so it shouldn't block UCM. Related: is there a best practice on doing deploys to unison cloud? Is it expensive to do ultra frequent deploys?
Here's another perspective by @dfreeman
Yep! IO watch expressions would be a nice ergonomic replacement for some of the simpler hotswap use cases and would make the more complex ones work more smoothly, since you could do io> do TokenMapping.send myTokens and not need to worry about it not rerunning in the case of a cached result (plus you could avoid the coerceAbilities hack)