I haven’t written any Clojure for a few years now. So my knowledge and technique is out of date, but I have a contrary opinion on “repl driven” development vs many Clojure advocates. In short: your repl compiled code is state that you now have to track in your head.
Yeah, great, you can compile a single function and it hot loads. It’s pretty awesome for experimenting and debug, but the longer your repl session is open and the more you change and compile, the more likely you are going to end up with a mixed state in memory that does not reflect how your program would behave if run from scratch.
You are going to forget to save a change or your manual tinkering with a data structure will leave it inconsistent. These errors accumulate, like navigation error with an IMU.
It’s got use cases for sure, and I miss hot reloading when I’m debugging in other languages, but it is far from being the ultimate feature a language can have.
I was very productive in Clojure for some number of years and never once, not a single time, found benefit in REPL-driven development. I've used it here and there just to test little snippets, but never actually used it for real.
Frequently I found it unnecessary, anyway. Clojure just made it way to easy to get things right that I rarely had to dive in and figure out why something was wrong.
These days I prefer types over Lisps for that, but people change.
Totally agree. I found Clojure to be an amazing, well-designed language. I found the REPL to be secondary to really great immutable data structures and pragmatic escape hatches.
I think this gets to the issue that there are two different ideas of "REPL driven".
In one, you're using the REPL frequently in production. In the other you're using it during development. Those are two different scenarios and result in different perspectives.
Just like many (most?) organizations have gone to (varying degrees of) infrastructure as code versus tweaking each server manually, REPL-in-production is similarly problematic. As you noted, you end up in a position where you aren't actually sure of the system state (both what it is and how it got there). This is a use of the REPL that should be minimized (if you're using it to actually change the system state versus trying to understand the system state, especially).
However, when working on a new system you may tweak a single or small cluster of servers manually and build out your automation scripts based on that experience. Similarly, the REPL during development is a way to quickly prototype, experiment, and test code but needs to end up committed to actual source files for deployment.
If you write your code the correct way, there wouldn't be any states in your namespaces for you to keep track in your head. That only happens if your code is full of `(def some-state (atom {}))` or something like that.
You are basically illustrating my point about "You probably cannot learn Clojure all by yourself". You need a mentor to show you how to do Clojure correctly, or you have the humility to learn the proper Clojure way. Sadly, most people would rather just try something and jump onto conclusions immediately.
Function definitions and vars are a form of state that can accumulate in a REPL session. Its not just atoms.
This can lead to issues where you decide to refactor and rename a var (Clojure is all about concise and expressive names, so its not uncommon to do this.. At least not for me) but you might not catch every place the old var name is being used. Because both vars are still exist in the state of your REPL, everything seems to work, but once you compile your uberjar or whatever, things are broken.
A REPL isn't a replacement for good unit tests, and there are techniques for reloading your REPL state on save, but the issue of needing to keep the "REPL state in your head" is real, and not an artifact of doing things wrong. Just something that the developer needs to be aware of as a potential hiccup in the REPL experience.
Then you don't need a REPL. This is a very weak argument, overall: there are programming techniques to compensate for any deficiency in the environment. The fact that you need to use them is already worse than not having to worry about it at all.
> Sadly, most people would rather just try something and jump onto conclusions immediately.
Just a gentle reminder, you're not talking to "most people" here. Nevermind me, we [EDIT: cut out the irrelevant details] I just want to say that assuming good faith and informed criticism are the better defaults for this forum.
Nobody said you "need" REPL. Doing REPL driven programming is just more productive, hence it is considered the right way in the Clojure community. Heck, you don't even "need" any higher level languages, you can write code in machine code, but that's besides the point.
Your boast about your Lisp credentials is exactly the kind of things I caution against in the article. Your pride has prevented you from properly learning Clojure, which has a very different programming style from other Lisps.
You are basically illustrating my point live, again.
> Yes, if you are not using the REPL, you are not doing Clojure right.
or should I understand this another way?
> but that's besides the point.
True. I'm saying that if you need to code in a particular way to make good use of the REPL, then it's already worse than being able to use the REPL well without any special coding style. Erlang and Elixir are great examples of that.
I also have a problem with you believing there even is one correct way to use any language, but that's bearable. You being so sure that you're the only one qualified to know anything about that correct way is much worse.
EDIT: I just saw so many "revolutionary ways to write code" which were being pushed hard by people absolutely sure their methods are so much better, and which ultimately amounted to little more than a few percent improvement in productivity in the real world - in the best case. It makes me suspicious of any such claims.
> Your boast about your Lisp credentials
My? I said:
> Nevermind me, but [...]
So you're putting words in my mouth; further, I said nothing about my knowledge of Clojure, so this:
> Your pride has prevented you from properly learning Clojure
Is completely unwarranted. And offensive. Could you please stop?
Then it is actually far worse than what I guessed. You know next to nothing about Clojure, but somehow feel that you are qualified to have an opinion about it. That doesn't look good, does it?
What opinion about Clojure?! Where? You're jumping to conclusions without any basis, while trying to caution others not to jump to conclusions?
I'm talking about the general ease of use of features in programming languages. If you need to code in a particular way to use a feature, then it's harder to use than if you didn't have to do this. Is that wrong? Could you provide any argument on why is it wrong? And yes, I use Clojure's REPL as an example, and give Erlang as a counterexample. Do you know Erlang? Are you able to compare? If yes, why don't you write about how you see that comparison?
Instead of, you know, childishly fixating on things I never said. For your information, I do know more than "next to nothing" about Clojure. But you won't believe me either way, will you?
I think this is caused because of the disconnect between the source in files and an environment in a REPL. There are alternatives:
You can keep your source inside your environment, like in Smalltalk. Whatever state you modified, it stays modified as long as you use the same image.
You can ignore hot reloading and have short-lived REPL sessions for testing and debugging. This works with Scala, OCaml, Idris, and similar. You don't need to track state too much, as it's frequently reset to known-good defaults.
Erlang allows hot reloading, but of whole modules only. You can only evaluate expressions in the REPL, not definitions. It's a good strategy, because the state in Erlang is rarely shared across modules, and having to reload the whole module ensures that it's always initialized correctly.
I think Clojure model is very close to what Racket has: you can switch between modules in the REPL and mutate their namespaces without ever reloading them from disk. Common Lisp is also close, the difference being that CL is image based, so there's a way for persisting the state changes... even if it's not the best of ideas :)
I like the Erlang model, but they all have advantages if used correctly. Pervasive immutability and sane reloading strategy help a lot, but even without those, you can use the REPL effectively. On the other hand, "living in the REPL" all the time is maybe going too far.
> but the longer your repl session is open and the more you change and compile, the more likely you are going to end up with a mixed state in memory that does not reflect how your program would behave if run from scratch.
I've seen this happen multiple times including during live Clojure talks at conferences. I also never found REPL driven development all that beneficial outside of quickly toying with trivial pure functions and that's possible in basically every popular language these days.
TL;DR -- Possibly it has to do with the style that I use, but those things doesn't actually happen to me very often, and when they do it's not a big deal. For me, the benefits of REPL-driven-development far outweigh occasionally having to deal with these issues.
Two things. First, I've been bitten by this myself, but it's pretty much always been something like "Oops, I renamed a function and forgot to update a reference to it, but the code still worked because the old definition was still there... until I reloaded the REPL." It's always been a trivial fix for me, since when I try to re-evaluate the buffer containing the outdated function reference it throws a "cannot find function definition" error. I currently consider the advantages of REPL-driven development to outweigh that occasionally happening.
Second thing: maybe others do it differently, but I don't think I generally make manual changes to the state while things are running. If the state doesn't look the way it should, I either start iteratively tweaking functions (by modifying my .clj file and eval-ing them) and re-running with fresh state to understand and resolve the problem, or I work with a separate, similarly-shaped piece of state and run it through functions in a sort of "manual unit testing" to tweak and fix whatever's wrong, and then I re-run everything from scratch to make sure it worked. I don't adjust the state, evaluate a code path, and then continue on if it works (because yeah, I can totally see how that would lead to the sort of situation you describe, which sounds unpleasant)
When doing REPL dev with clojure, I usually never type directly in the REPL, it's connected my editor and there are shortcuts for evaluating forms already typed in there (e.g. form under cursor, form before cursor, outer most form, entire file) and it outputs the results right after the form (as a temporary display or can even be output the text into the file).
There's also the handy "comment" function that lets you do something like (comment (+ 1 1)) in your code file that won't get compiled but keeps all the editing benefits (syntax highlight, structural editing) that you wouldn't get if an expression was commented out using comment syntax.
This comment reveals a view of REPL for non-image based languages.
In clojure, we usually add forms (like functions) to the editor then eval them to update the namespace hosted by a separate REPL running in background.
Yeah, great, you can compile a single function and it hot loads. It’s pretty awesome for experimenting and debug, but the longer your repl session is open and the more you change and compile, the more likely you are going to end up with a mixed state in memory that does not reflect how your program would behave if run from scratch.
You are going to forget to save a change or your manual tinkering with a data structure will leave it inconsistent. These errors accumulate, like navigation error with an IMU.
It’s got use cases for sure, and I miss hot reloading when I’m debugging in other languages, but it is far from being the ultimate feature a language can have.