The problem with REPL-driven development is that when taken too far it makes you capable of creating programs that simply cannot be understood by someone without the "lived experience" of the path taken to arrive the solution in the REPL.
The very property that makes it liberating - that you can instantly try things out and see mistakes - allows you to elevate up a level or two in complexity of what you can do. But the future readers of your code don't have this advantage.
While you can apply discipline and use it to work to find the simplest, clearest solution, there is nothing that forces that and you can just as easily arrive at something that looks and works like magic to someone coming to it fresh.
I don’t know. I can write incomprehensible code in any language. It is to some extent the default. I have to work hard so as not to be the poor future reader (myself) in 6 months thinking “where do i even begin…?”
I find the repl encourages me to iterate quickly and when i set out to solve a problem, one of my goals is to express the solution in a nice way.
The repl just reduces the cost of me trying different ways to achieve that. Whether i leave behind horrible or nice code isn’t really a factor of the repl, it’s a factor of whether i started with an intention to write understandable code. I usually do and the repl’s instant feedback and state retention makes that vastly easier.
Sometimes i don’t set out with that intention though, i frequently have to process lots of text and regexp is just the easiest way for me usually - even though i accept it’s utterly impenetrable to many once you go beyond 10-20 chars or so in an expression. No repl involved in producing fairly impenetrable code.
I’ve been reading the book ANSI Common Lisp by pg, and decided to work through some of the exercises that piqued my interest.
Similar to what you are talking about here, I worked on one of the problems for a while. Not in a REPL, but in a similar manner. And I arrived at a solution that is not advanced or anything, but which I was quite satisfied with. It occurred to me then that my solution to the problem I was working on really only made sense because I had worked through the problem.
For this reason I left a printout of two of my hash tables in the code. Because when you see what data they actually have in them it becomes quite obvious what the code is doing. Whereas without those two printouts you’d have to “simulate” in your brain what the code is doing.
And so for that reason I left the printouts in my code.
But this also ties into a more general problem in software development which is that even though our tools are becoming really really good and our computers really really fast there is still a long ways to go in terms of how deeply and how completely we can really see what’s going on.
I was thinking about this recently when I was at the dentist to pull a tooth. It was not a fun experience but even so I saw that the dentist had both the skill and the tools for making the operation. And in particular the tools he had at his disposal allowed him to understand and to operate on my teeth. And that got me thinking once again about the lack of visibility that we have when we develop software.
> But the future readers of your code don't have this advantage.
Future readers also don't have access to my brain state when I'm implementing the program. Whether in Lisp or in Haskell or in C or in Python. This is kind of a nonsensical point. Iterating over a solution in Java and in Lisp it amounts to the same thing: code in a file. There's nothing keeping you from documenting.
> The problem with REPL-driven development is that when taken too far it makes you capable of creating programs that simply cannot be understood by someone without the "lived experience" of the path taken to arrive the solution in the REPL.
Can you provide an example for this? Intuitively I'd say you are right, but, OTOH, even after 20 years of CL-hacking (which probably does more REPL-driven development than any other language), I cannot come up with an example...
I have, once or twice, ended up in a position where "code saved in file" would load perfectly fine in my hacked-in image, but fail to load (or compile) in a fresh image.
This is (usually) a sign that I forgot one function or macro in the code-on-disk, that I hand-chased in the REPL. In neither case was it hard to rectify, but it did leave me a bit confused. And very thankful that I had not quit the main dev image.
After that, I have explicitly added "add a test suite, run test suite in fresh image" as a technique to keep that from persisting for a long time. And simply doing that seems to keep me subconsciously aware of the problem and not making it appear in the first place.
I would argue something else: the repl invites you to write functions that take all their state as input, due to that making repl working easier. That means figuring the code out can be the same as writing it.
Now, sure, you can write a mutating statefull mess using the repl as well, but most code I have come across in CL is not like that, except for maybe the odd times you find java-in-cl.
It is not the same thing, of course, but it is tangential. Pure functions is something you can write in any language, but languages where you so easily can develop/try smaller chunks make it a particularly good idea. Trying a piece of Java code usually means running a lot more code that sets up your state for you. How I, and most people I know, use a lisp repl does not.
This is only a problem when the image diverges from the source. I heard of cases where there was precious functionality only compiled in the lisp image without any source code because it was programmed and debugged directly into REPL. But this is a mistake of programmer misusing a powerful tool, not a good practice.
Emacs makes doing this too easy, though. I prefer slimv which has no extra repl buffer. It forces you to edit the sources and invoke REPL with snippets from there - ideally, whole file stays compilable all the time. Or at least to use a scratch file, to later get back to and refactor. Any programmer bitten by their own unreadable code should learn to do that and resist siren calls of repl patching. But then, unreadable code is possible in any language.
I rather like the Interlisp-D approach where while you edit in a live environment it then syncs the modified versions back out to lisp source files on disk as its standard/natural workflow.
I suspect the way you're working with slimv is at least somewhat comparable in terms of making getting that part right the path of least resistance, which is itself a form of developer experience ergonomics.
The very property that makes it liberating - that you can instantly try things out and see mistakes - allows you to elevate up a level or two in complexity of what you can do. But the future readers of your code don't have this advantage.
While you can apply discipline and use it to work to find the simplest, clearest solution, there is nothing that forces that and you can just as easily arrive at something that looks and works like magic to someone coming to it fresh.