upvote
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.

reply
Being someone who’s used to the “typical REPL” flow, I’m not sure I grasp what’s going on with the no-restarts. The implications I think I see are:

* Clojure is built different in terms of hot code reloading

* the REPL is its own application process in languages Ruby or Python, but in Clojure it’s sortof a client for the system

Is that right? Is there more to it?

reply
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.

reply
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.

reply
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.

reply
> 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.

reply
Is this similar to Unison scratch file driven development?
reply