Ya, I like the exploratory nature of a REPL and wouldn’t want to give that up. But I also have tests. Now I mean, if writing a test and running the test basically entails almost the same user experience, we can probably consider them same.
So, one thing already that I think is much similar between the two is that when I say REPL in Clojure, I don’t mean the command line input based one. I mean pressing Ctrl+Enter anywhere in my source code and having the form at cursor executed and returning its result.
This means I’ll have commented forms either with comment or #_ that perform setup and teardown of certain resources for when I want to be able to run any form in the code and have their necessary environment or inputs available to me.
Once my function starts to take shape in terms of what I really want it to do (generally happens after some level of REPL based exploration), I might open up the corresponding test file and start writing test cases there. Though it depends, because in my REPL I often go the extra mile and setup full integration state, and in my tests I might restrict that more to unit tests, or if I think necessary I might go out of my way and have an integ test as well, but the work involved to stand up state for an integ test often is more than in the REPL, so I don’t always.
At this point, my tests are still in my REPL even though they are also inside my test file. Because my files and my REPL have the same UX. So I’ll be re-running my tests through the REPL still.
So that’s my workflow. But, if instead of the REPL, you could very quickly start and setup/teardown your tests. I guess I could in theory use them for exploration as well. I mean, that’s what I used to do in other languages. Except starting and running tests in the languages I’m used too, Java, Scala, C#, C++ is nothing that’s easy or quick. But if you were in a place where the only difference to what I described above is that you are restricted to Ctrl+Enter being allowed only in the test files, and where instead of running the form at point, it ran the test at point, and the speed of bootstrapping is just as fast, ya, that gets real close to having the same user experience I think. With the benefit like you said of isolated state and always starting things from a blank slate so the last execution can’t affect the next one. So there’d be that. Though sometimes I choose to keep the last execution state around on purpose, with tests I’d have to do a bit more copy/pasting to carry over the setup of one in the other and all, but I guess that’s manageable.
Where I think REPL driven development goes even beyond this though, is something that in all honesty Clojure also doesn’t fully realize. But if you ever try and code Emacs you get the full beauty of the idea. Being able to modify the running production program in real time. Hot Swapping logic in and out, have a bug? Just fix it. Don’t even need to restart and you fixed the issue + you saved the fix back to the program source file so when you restart the fix will still be there. Now for big enterprise service and all, that might be playing too fast and too loose. But from a usability as a programmer, man, if every app were like Emacs (or if Emacs had become the Linux desktop manager as it was envisioned and all desktop apps had been Emacs apps) that would have been an awesome (maybe except for performance) world.
I do use REPLs in production as well, mostly for debugging, and the source of the app on a server isn’t under git control, so it’s not exactly the same flow. So it’s more of a convenient debugging tool for being able to run certain things and inspect state in prod.