When I'm working, there is little or no migrating of code from the repl buffer to a file because it's already all in a file. I almost always work in a file. I write snippets in a file, send them to the repl with a keystroke, and build up the world incrementally as I discover what it needs to be. As the contents of the file get larger and more complicated, I move things around and organize them. It's a conversation with the repl, not an editing session in the repl window.
I don't consider any Clojure tools I know of to constitute a proper repl-driven environment, precisely because the language and runtime lack support for the kinds of programming and debugging that I've taken for granted for decades. If I can't inspect and edit and redefine everything in the runtime, it's not the full, proper set of tools.
I've written a good bit of Clojure code, and I'll happily do it again if I need to do something that isn't convenient in, say, Common Lisp, but I consider it an acceptable alternative to things like Haskell and Scala and F# and Swift, not an attractive alternative to Lisp and Smalltalk.
I do occasionally need to restart a Lisp environment because of some gnarly breakage I've committed, but it's pretty rare. Moreover, killing and restarting my favorite lisps takes about--wait, let me check--okay, a second and a half to kill a live app in staging and have it back up, fully-functioning.
I also tend to often just reload changed namespaces and resetting the (component/mount/integrant based) application too. Or letting shadow-cljs live reload my clojurescript. But having the REPL directly accessible is still super useful.
You calling anything else REPL is just wrong, misleading and confusing.
If I'm going to enter "(+ 2 2)" using my normal development tools, I'm probably going to start Emacs and tell it to start a SLIME session with one of the Common Lisps I use. When I do that, it'll create a repl buffer, because that's the way I have slime configured.
I mean, I don't have to load slime-repl. I could just interact with the Lisp's read, eval, and print functions directly from an arbitrary buffer without involving the SLIME repl display at all. But, y'know, force of habit. Also, I do occasionally type something in SLIME's repl buffer, especially if I want to see some big wad of output and don't want it spewed into the middle of the expressions I'm working with.
Most likely I won't type "(+ 2 2)" in the slime-repl buffer, though. I'm more likely to type it into a scratch buffer and send it to the Lisp by hitting C-x C-e. As soon as I start typing Lisp code, I know I'm probably going to want to add some more and maybe edit it and probably send it to the Lisp again. That's just more convenient if I have it sitting in a buffer in front of me, and not scrolling off the top of the repl buffer into infinity.
So am I working in a repl? Lisp is running a loop waiting for input. I'm sending expressions to it. It's reading them, evaluating the s-expressions produced by READ, and then printing the resulting values to a stream I can look at. Sounds like a repl to me. It's what I've always understood a repl to mean, including when I'm building them.
Does it count as working in a repl when I use a keystroke to send "(+ 2 2)" to the Lisp from the scratch buffer, or does it only count if I actually physically type the text in the buffer where the Lisp displays its prompt? What if there is no prompt? What if I start Lisp and SLIME but don't load the slime-repl extension? Does that mean that the loop that is reading, evaluating, and printing stops being a repl?
I'll probably save my scratch buffer to a file at some point, if there starts to be enough text in it that I think I might forget some of it. Does it stop being an interaction with the repl the instant I save the buffer to a file? I can save the slime-repl buffer to a file, too; does it stop being a repl when I do?
What about Lisp and Smalltalk environments where the read-eval-print loop doesn't display a prompt? Take a Smalltalk or INTERLISP worksheet, for example. Is it a repl if there's a loop reading, evaluating, and printing expressions, but no prompt? Does it stop being a repl if the incremental inputs and outputs get saved to a file? Does it count if it's not a text file, but the environment automatically saves the state of the worksheet to an image file that it automatically deserializes the next time you start the environment?
I don't think I'll adopt your lexicon, but I am sort of curious where its boundaries are.
It is not “interpreting or compiling a file” if the tool you are using to evaluate code in the file is sending isolated blocks of code to a REPL and not, well, interpreting or compiling the file.
> REPL is opening interactive mode (if the language supports it) and entering "2+2".
No, a Read-Eval-Print Loop (REPL) is not exclusively used by a user typing keystrokes at a terminal; like other terminal software, it is a candidate for being driven by other programs.
> You calling anything else REPL is just wrong, misleading and confusing.
You insisting a REPL isn't a REPL when the “Read” part is reading something other than keystrokes directly entered by a human user is just wrong, ignorant, and confused.
The main thing that stands out to me is playing with small snippets of code in real-time as you're writing it. Contrast that with writing a class and methods, then writing unit tests, then running them. There might as much as ten minutes between the time you start writing your code and when you run any portion of it, and always running large chunks of changes.
Test-driven development as a discipline emerged from the Smalltalk community, and I don't think that's an accident. I think it's a formalization of the way that Smalltalk and Lisp programmers naturally tended to work.
I generally start on something by making a naive model of what I'm trying to accomplish and interrogating it. In successive iterations, I modify the model and the interrogations in the direction of what I need it to be, discovering details along the way.
I'd say that a majority of my actual activity is testing. That's one reason I prefer to interact with a repl from a file: because I want a record of my interactions. I want to be able to do them again and again.
The difference between that record and formal tests is a simple matter of copying the trail of my interactions into a test framework, and that, too, is how I normally work: make model; test it with interactions; keep the interactions around for later.
As I make progress, the models turn into data structures, and the interactions turn into functions and tests.
It may also be faster to find an issue in the REPL, using various tracing tools ("hm, this function returns the wrong thing when I feed it X, let us enable tracing on a few things and see if anything stands out", followed by "aha, that other function over there is actually the problem, let me add some test cases, so we don't have a regression there").
None of these are impossible without a REPL, but may require extensive modification of the source and a recompilation to get the same effect (followed, probably, by either converting your tracing to debug logs, guarded by a verbosity flag, or by painstakingly track down each print and rip it out again).
When I do REPL driven development in Clojure, that's exactly what I do and that's what other folks that I know mean by REPL driven development (in Clojure): define types, functions and variables in a source file, often in a (comment) form, eval them one by one, and copy the code out of the (comment) form when it's stable enough. I wouldn't type code directly into the REPL; that's not a pleasant experience in Clojure - but might be pleasant in Common Lisp or Smalltalk for all I know. The process that I and others use in Clojure most definitely differs from the process that the OP describes and I lack enough familiarity with Common Lisp to know if the process is more pleasant in that language. I assume it is and I must make time to learn Common Lisp properly some day.
For any test/scratch code, just create a scratch file outside your project and write the experiments there, eval'ing as you type. That file could even be an "official" unit test you include in the project.
I spent years in Clojure and found that writing code in the REPL just... isn't fun. This has been true for every interactive system I've ever used (including... the shell).
The shell as a REPL for a succinct language is nice for interactive workflows, but for trying to build less ephemeral pieces of code, the editor is the first class citizen I care about. Sending code to a REPL to be interacted with? Cool, and I think this is what a lot of people mean when they say REPL-driven.
I'd imagine convenience around that in a language or its tooling would be what convinces me to do more REPL-style things.
I think this is a huge barrier of entry to languages like Clojure. The syntax and dynamic nature of the language really lend themselves to an extremely interactive and productive workflow, but that is a hard thing to communicate. It seems most people think that when Clojure programmers refer to the joy of the REPL they imagine writing code into Python or Ruby's REPLs (e.g. https://repl.it/languages/python3), but that is about as far from what is meant as is possible. I mean, who would enjoy programming in an environment that deleted your work every time you finished a line of code?
I mean, yeah. You just import the Python module you're writing and you can call any function or inspect any object you want. iPython even has hot reload
Unless you've figured it out? In which case please say, because I want to code python like this.
Specifically I write code in my editor, hit a keystroke and all the code at the cursor gets sent to my running python and evaluated. The editor needs to be aware of python's indenting rules so it can do this, but once again if you've worked out a nice way to do this please say. Minimal keystrokes required would be great =)...
I'm not really requiring that the evaluated output gets sent to the editor, though of course that would be ideal, because in the worst case I can side-by-side the editor and the terminal.
From my perspective jupyter notebooks get kind of close, but I'm not coding in a file, so it's not quite ideal.
One important thing to keep in mind though is that it is not as dynamic as Lisp, so things like re-evaluating a class definition does not update existing objects. I end up restarting the python process and evaluating the buffers I'm working on often enough that I've bound it to its own key.
I've not used emacs for python, but could give it a spin for this!
Bit unfortunate that you need to restart the process for re-evaluating classes though.
It's one of the main reasons why I love Common Lisp so much.
Still, it's not too bad and IMO a lot better then restarting the process on every single change
Docs: https://code.visualstudio.com/docs/python/jupyter-support-py A short demo on YouTube: https://www.youtube.com/watch?v=lwN4-W1WR84
Here's the fork that I'm currently using https://github.com/grusky/slimux
I use it with python and node.is and MySQL and psql
It's great because it can work with any REPL.
I used to use jupyter notebooks but almost exclusively use slimux now unless I need to some data visualisation or computer vision.
VS code's ipython REPL is quite good and has support for images and graphs.
I will say that the main thing that triggered a REPL driven workflow was dealing with large state. When you do machine learning and you have to deal with large models and datasets, loading them each time you save your code and triggering a unit test really gets tedious. You can definitely do it with caching and reducing the dataset size, but it only gets you so far IMO. There's nothing like being to inspect the state of your environment. Particularly when it takes a long time to create that state.
...I call this "copy/paste". And since I use X clipboard and a tiled window manager, it's only a couple keystrokes and no mouse. Highlight the code in the editor, "super+<direction>" to switch windows, "shift+insert" to paste.
Is there a shortcut to evaluate the code in the correct module/namespace for Python? Maybe to switch the namespace in which the Python REPL evaluates the code you paste in?
There's a reason I'm specifying specific actions, I can easily see increasing the cognitive burden breaking the ease of that workflow. You can't tell me that putting your cursor to a code block and hitting a key is the same as selection, copy, swap to console, paste...
I've certainly had situations where I've done what you're describing and then still taken the extra time to setup the workflow I've described above because it's worthwhile when you're trying to debug a knotty problem.
For adhoc testing code you can type on the repl buffer.
Yes, it’s nicer than Python’s. Yes, it’s convenient. No, I never ended up doing my development in the REPL first. Why? Because editing mistakes in a REPL usually sucks, because a REPL is not a text editor.
What I ended up using heavily was REPL to file integration, which gave me the ability to write a function normally, evaluate it in the attached REPL session, and then play around with it in the REPL. This is far short of the “REPL driven development” that’s commonly discussed, and frankly something that’s probably possible with the Python REPL if they wanted to.
Editing data and functions in the REPL is a neat trick, but it’s a double edged sword, because a REPL can crash, and it provides incredibly rudimentary support for diffing current state and migrating changes back to permanent files. I would never start with anything more than a trivial “how do I manipulate this list” in the REPL for that reason. Oh, and we had a lot of issues with REPLs getting into a bad state with Clojure due to multi methods and protocols; if you’re doing your primary work in the REPL, then having to restart it due to it becoming unstable really sucks.