Hotcaml: An OCaml interpreter with watching and reloading
github.com
github.com
I hated ML when I first encountered SML in college. As I became more familiar with functional programming, I started enjoying them. the ML languages just felt like such a better deal than the complexities arising from the purity of Haskell.
More intuitive and have records. Maybe they'll have a moment yet. Their learning curve might still be steep. I do hope for a wide spread variation to come out some day.
[0] https://www.youtube.com/playlist?list=PLre5AT9JnKShBOPeuiD9b...
This includes apps like TinyMCE, ahrefs, and around 50% of messenger.com code [1].
[1] https://reasonml.github.io/blog/2017/09/08/messenger-50-reas...
EDIT: Ah I just read https://github.com/let-def/hotcaml#synchronous-and-asynchron... so it looks like at least for the synchronous `hotcaml` bit it just completely reloads from scratch so state is not preserved, but I'm curious then how the `hotcaml_lwt` frontend deals with this issue.
I'm curious if there have been any similar things for statically-typed languages.
There is actually some notion of persisting state in it. Reloading is done at the granularity of a module. The reverse dependencies of a module that changed are also reloaded, but the dependencies are not. The runtime state of all modules that are not reloaded is persisted.
Here is an example: https://aws1.discourse-cdn.com/standard11/uploads/ocaml/orig...
The dependency graph is roughly: system layer -> renderer -> slideshow The system layer initializes the window (with SDL) and an OpenGL context. The renderer allocates resources (buffers, fonts, ...). The slideshow only defines the content to be drawn.
In the live code, only the slideshow is modified, so the system and renderer states are not affected.
Doing reloading at the module granularity meshes well with ML semantics: it is a straightforward extension of the "hyperstatic environment" and it preserves type safety and modular abstraction.
A static language that is designed with hot reloading in mind could do better, the generative nature of ML type definitions is not ideal.
I guess that structural type systems permit a finer grained notion of reloading. Actually, a finer-grained notion of persistence and state migration could be built as a library (with some macros) on top of Hotcaml. The important step is to be able to reify type definitions and then to define some notion of "type compatibility" (between old and new modules) by induction on the structure of types. This is a lot of work, but could be built on top of Hotcaml foundations.
Ah that makes sense to me, and explains to me why in your linked demo the rotation resets after every color change, but the other stuff doesn't. That does mean though that having the circle not reset after every color change is probably not feasible unless the module order is fairly convoluted right? (because most straightforward ways I can think of organizing the code would have the circle motion as a reverse dependency of the color of the circle).
Not necessarily no. The thing is that you could have an early module that implements a framework to manage state. For the circle rotation, it is quite easy, it is a single number. You could do something like:
`let circle_rotation = managed_state "circle_rotation" Float 0.0`
"The rotation of the circle is represented by a float initialized to 0". Then even when your module is reloaded, the hypothetical framework takes care of preserving this piece of state.
https://ipython.readthedocs.io/en/stable/config/extensions/a...
Launch ipython like you would `pdb`
https://pypi.org/project/ipdb/
*edit to @dwohnitmok as the author of Crafting Interpreters mentioned, it really works pretty well in practice. And for cases where I was changing enough state that it bails out, you can either set a breakpoint in a test or create a little closure to build up the right state and then pass back an fn to step through in ipython.
Infact, this dev style reinforces an immutable nearly functional data centric programming model. With that model, changes to code won't impact the data, so reloads are actually rare. Have shallow reference chains, use value types in simple containers and lots of problems kinda melt away.
Ha! https://news.ycombinator.com/item?id=29965226 the highest rank post on Google search for that quote is less than 3 days old. Know we know why results are so bad!
So the main thing I'm curious about is the exact mechanics of this:
> And for cases where I was changing enough state that it bails out
What exactly does "bailing out" entail?
I'm curious though what happens if you have to make changes to that data store in a statically typed language like Dart, e.g. change the type of a field. The docs there only seem to have a few sentences about that (which I think is talking about a related, but slightly different thing, which is that hot reload allows a whole new host of paths to create state which might've been impossible to ever achieve during the normal, non-hot-reloading operation of an app, including violating certain assumed invariants):
> If code changes affect the state of your app (or its dependencies), the data your app has to work with might not be fully consistent with the data it would have if it executed from scratch. The result might be different behavior after hot reload versus a hot restart.
Does the app crash? Or does it automatically restart with a fresh slate (maybe this falls under the "some kinds of structural changes that necessitate a full restart that wipes state away" you mentioned)? Or is the change ignored? Or is there some degraded "delayed type checking" mode that occurs with hot reload that changes a type into a runtime type guard and causes some sort of exception for anything that requires the field to be a certain type?
You may think of it that way conceptually but the language and VM don't think of it that way. It's all just objects in memory in the heap. Architecturally, there's no real separation between persisted and non-persisted state.
> Does the app crash? Or does it automatically restart with a fresh slate (maybe this falls under the "some kinds of structural changes that necessitate a full restart that wipes state away" you mentioned)? Or is the change ignored?
I'm not an expert on the details, but I think it simply won't do the hot reload if it determines that it can't do so safely. It's up to the user to then choose to do a full restart and wipe the state if they want.
I will try to make a parallel with a simple Makefile-driven project.
In the initial build, all files are built, following a topological ordering of the dependencies. For the Makefile it means invoking some unix command for each intermediate artefact. For Hotcaml, it means parsing, typing, compiling and loading each module.
After that, build is incremental: when a file is modified, only this file and its reverse dependencies need to be rebuilt. Make will keep the existing artifacts that are not out of date. Hotcaml will keep the existing modules that are not out of date. And their state is preserved.
Now, the synchronous frontend is quite like a regular Makefile: a build step is considered done when the build command finishes. The asynchronous frontend allows command to run in the background, like a Makefile that would spawn processes/daemon using the shell & control operator.
In Hotcaml, these threads continue executing concurrently with the reloading process. They can be notified that their "host" module has been reloaded by registering with some runtime service (via a module named Hotlink).
[0] https://docs.microsoft.com/en-us/visualstudio/debugger/edit-...
It's been a real PITA to crash out of an interpreter then reload it again. Dune helps in that it will load the libs for you, but you've still got to open modules etc.
Or create a custom top level if for some reasons you prefer the extra modules to not be distinct files.