Modern hot reloading and how lispy REPLs work are similar in many ways, i.e. they allow you to make changes to a running application without stopping it, however there are differences in their experience and how they accomplish it.

The primary difference in experience is the unit of change that you can make with these systems, which is typically based on the unit of compilation of the language you're using.

For example if you're using a language whose unit of compilation is a file, IME you must reload every change in the file at once. This has upstream effects to how hot reloading is accomplished, because if you're reloading an entire file where you've only _actually_ changed one function, then your hot reloading system has two options:

1. It reloads everything in the file, meaning you're potentially losing state that could have been preserved across the change. This typically leads developers to spread definitions across many files in order to be able to make more fine grained changes to their running system.

2. It uses some heuristic to detect whether state should be preserved or not, typically via some static analysis. This can be opaque to developers working on applications, and adds a lot of complexity to the hot reloading system, but is probably the best one can do if your unit of compilation is a file.

Lisps, on the other hand, typically make the unit of compilation a "form", i.e. a well-formed syntax tree like (foo (bar baz)). This, combined with some editor tooling, means that I can make extremely fine grained changes to the system by selectively evaluating forms within a file, preserving its state except for the form that was evaluated.

The way this works in practice is: I have my editor open and connected to my running application using an extension. I place my cursor on a form I have changed and use a hot key to compile and send that form to the running application. I can view the return value of that form in my editor, and if there were any side effects (e.g. re-defining a variable or function), my application will now exhibit that new behavior.

Side note: the benefits of being able to evaluate forms and see their output in my editor is hard to state. It turns your editor from a one-way communication tool (I make changes, hit save, and those changes get made to the application) to a bidirectional tool, where I can both change and inspect the running system. I wish more languages had robust tooling for this.

The other difference that you tend to see between languages (even between languages with just hot reloading) is late vs. early binding. Lisps are typically very late bound, so you don't need to recompile and/or restart your entire application on every change.

Other languages who bind earlier have to deal with stale bindings. Even JS hot reloading has to deal with this problem in complex ways because most bundlers use closures as modules and bind their inter-module dependencies on instantiation, which means that even though you in theory don't need to recompile or reload anything except the one JS function you just changed, in practice you have to reload the file and any files that depend on it, which puts us back in the position of needing to use some sort of static analysis to detect what has actually changed in order to precisely invalidate application state.

I hope this mountain of text helps highlight the subtle differences between hot reloading and a lisp REPL. It's not just the tooling but the specific semantics of the language that make a difference when trying to make changes to a running system.

Source: my experience working with and developing tools for hot reloading and REPL development in JS, ClojureScript and Clojure