Almost exactly, it's mostly how you use the REPL that differs, and then only because of what different editors prioritise. When I'm in Emacs, all my work happens against a running REPL - when I open or save a file, it's reloaded. Any tests loaded in the REPL rerun on every save, within that live instance. If I drop into the debugger, it's against that live instance. I can swap in mock components to a running system, go check stuff in a browser (even jack into a live webpage with ClojureScript), all in one long running instance. I have struggled to recreate this kind of setup as smoothly in Python with any editor (pytest doesn't want to run this way, and IPython's autoreload doesn't feel as reliable), but I do probably write more REPLy code in Python than most, so all my model training and optimisation runs during development happen in pausable background threads in IPython etc.
All that said, 90% of the time you still just eval a bit of a code to see what happens and that's the same between the two languages.
(you can theoretically pass "reload": true (or similar option) in launch.json for auto reload, tho I haven't felt the need to use that in my workflows.)
the benefit of lisp in an editor with integrated repl environment is that you get to see immediate feedback by easily evaluating chunks of your code as you navigate it, without copying stuff back and forth between the editor and the repl
and the benefit of lisp comes from the fact that every expression is fully delineated by parentheses, so you don't need to select any text, find the start or the end of any syntactic construction, you just eval the form you're standing at and see the result
or just as easily you can wrap that form, bind the locals to test values and eval that to see what changes
An example of me solving an Advent of Code with clojure and repl. You can see i never interact with the repl directly, I just send code to it via my editor and get results inline.
You evaulate code within your editor against the REPL, seeing the output in the same window you're writing in (perhaps in a different buffer).
The cycle is:
1. Write production code.
2. Write some dummy code in the same file (fake data, setup).
3. Evaluate that dummy code. See what happens.
4. Modify code until satisfied.
Your feedback loop is now single digit seconds, without context switching. It's extremely relaxing compared to the alternatives (rerunning tests, launching the program with flags, what have you).
Indeed. For people used to the "typical REPL" from Ruby, Python and alike, the best comparison I've found is this:
"Typical REPL" workflow: Have one editor open, have one REPL open, have one terminal open that runs the application. One change is typically: Experiment in the REPL window, copy-paste into your editor, write tests, restart application (lose all state), setup reproduction state, test change. Or something like this.
In a Clojure REPL workflow, you'd do something like: Have one editor open, this starts the REPL and often the application in the background too. One change is typically: Edit code, evaluate that snippet of code (which sends it to the REPL and the running application), write tests, evaluate them too in the editor, if you're happy, hit CTRL+S and you're done. Application still has the existing state, no restarts needed and you essentially never have to leave the editor window/pane.
Of course, others might have slightly different workflows, but for myself and many (most?) other Clojure developers I've observed in the wild, this is pretty much the standard.
Yeah, Clojure code is typically built with this in mind, and the data structures as well.
So in a JavaScript server, you might have the database connection set in some config/thing you pass around to request handlers to use, and if you change a request handler, you typically stop the entire application, then start it again. Then the database connection (and the entire config in fact) would need to again connect.
In a Clojure application, first you either start the repl from the server, or start the server from the repl, then you might instead keep the database connection in an "atom" that you keep around for as long as you have the repl/server running. And instead of restarting the process when you change a handler, you'd only change the handler.
And yeah, the libraries and ecosystem at large is built with this (mostly) in mind for everything. The language also supports this by letting you "redefine" basically anything in a running Clojure application, which would be harder in other languages.
I've done some experiments years ago for doing the same in JavaScript, and it kind of works, but every library/framework in the ecosystem is not built with this in mind, so you'll be writing a lot of your own code. Which, now with LLMs, maybe is feasible, but can't say it was at the time exactly.
You're on to something. It's the lisp machine of it all. Hot reloading is nothing that requires anything special, so you can redefine a callback or dependency with ease in the repl and the system chugs along. You can theoretically do something similar in ruby, but it's the opposite of elegant, you'd be forced to re implement methods with different dependencies etc. It's also a function of being "functional" in the lisp sense, that things are lists, and lists can be replaced, functions or otherwise.
The fun way to get a feel for lisp machines is emacs, it's so easy to fall of a language and especially hand-coding in a language if you don't have to.
You don't need a repl for this workflow and it can be easily implemented in any language. `ls *.MY_LANG | entr -c run.sh` You get feedback whenever you save the file.
Personally, I find waiting more than 200ms unacceptable and really < 50ms is ideal. When the feedback is very small, it becomes practical to save the file on every keystroke and get nearly instantaneous results with every input char.
> You don't need a repl for this workflow and it can be easily implemented in any language. `ls *.MY_LANG | entr -c run.sh` You get feedback whenever you save the file.
As in restarting the entire program and re-running every subsequent query/state changing mechanism that got you there in the first place, being careful not to accidentally run other parts of the program, setting it up to be just so, having to then rewrite parts if you then want to try something somewhere else?
Perhaps I'm misunderstanding you, because that sounds horrible unless you're scripting something small with one path.
The whole point of the REPL is that you evaluate whatever functions you need to get into your desired state and work from there. The ad hoc nature of it is what makes you able to just dig into a part of the code base and get going, even if that project is massive.
> Personally, I find waiting more than 200ms unacceptable and really < 50ms is ideal.
Calling a function on a running program takes microseconds; you're not restarting anything.
More or less, yes. It's more about the approach to the repl and how it is leveraged in development, or even jacking in to a running system and modifying it as it is running.
All that said, 90% of the time you still just eval a bit of a code to see what happens and that's the same between the two languages.