I've answered similar questions several times over the past few years, but I don't mind repeating myself. It offers me a glimmer of hope that my preferred way of working may not fade away, after all.
Consider the standard Common Lisp generic function UPDATE-INSTANCE-FOR-REDEFINED-CLASS (http://clhs.lisp.se/Body/f_upda_1.htm). It reinitializes an object when Lisp detects that the object's class definition has changed.
Ask yourself this: who would call such a function? Why would anyone ever invent it? Not only did someone invent it, a committee of some of the world's smartest and most experienced Lisp programmers wrote it into the ANSI standard for the language. What were they up to?
UPDATE-INSTANCE-FOR-REDEFINED-CLASS is not a weird anomaly; it's part of a carefully-considered set of features and protocols designed to support a specific style of programming. The Lisp runtime calls it for you automatically when it touches an object whose class definition has changed.
If you've defined a method specialized for it, then Lisp executes that method to rebuild the touched instance as if it had originally been instantiated from the class's new definition, and then your program goes its merry way. If you didn't specialize UPDATE-INSTANCE-FOR-REDEFINED-CLASS for this case, then the Lisp drops you into a breakloop.
A breakloop is an interactive repl with full access to all of the runtime's memory and all of the language's features, including visibility into the whole call stack that landed you in the breakloop. You can wander up and down the call stack, inspect anything in the runtime, edit bindings, redefine types and functions, and resume execution either at the point of control where the breakloop started, or at any other point for which the breakloop exposes a restart.
UPDATE-INSTANCE-FOR-REDEFINED-CLASS is not the weird fever dream of a confused eccentric. It's part of a purposeful system design intended to support a style of programming in which you build a program by interacting with a live runtime and teach it, interaction-by-interaction, how to be the program you want, while it runs.
It's a particular example of a general approach to programming best exemplified by these old systems. That general approach is the answer to your question: "Can someone knowledgeable explain how are lisp REPLs different from Python / Ruby REPLs? What is the differentiating point of REPL driven development?"
The differentiating point is that the entire language and system is thoughtfully designed from the ground up with the assumption that you're going to be changing your work in progress while it runs, and that you should be able to change absolutely anything about it as it runs and have a reasonable expectation that it will continue to work while you do it.
I like to call this style of programming "programming as teaching", and distinguish it from the much more widespread "programming as carpentry", in which the programmer is, metaphorically speaking, at a workbench banging together artifacts and assembling them to see how they turn out.
To be clear, I do not claim that the teaching approach is objectively better than the carpentry approach. I claim only that I, personally, am happier and measurably more productive using the teaching approach. I know that some other programmers report the same thing, and I suspect that if the teaching style of programming were more widely known, then there would be more programmers who prefer it.
There are several sibling comments that assert that any language can be made to support repl-driven programming, or that offer various languages and systems as examples of repl-driven programming. I'm sure that's all true, for some relatively restricted version of repl-driven programming, but the gold standard in repl-driven programming is programming as teaching in the style of old-fashioned Lisp and Smalltalk systems. These old systems offer amenities that the younger alternatives touted here do not match. I want more people to be aware of what they're missing.
Starting in the 1980s, I grew accustomed to systems that could start from cold in about a second, presenting to me a complete interactive development environment with all tools preloaded and ready to work, with the whole dynamic environment of my work in progress in the same state it was in the last time I was working with it. Moreover, I was accustomed to being able to take a single file from one machine to another to reproduce that same whole working environment equally quickly and easily on the new machine.
I could save the entire dynamic state of the running system to an image file, a serialized version of the running system's memory. I could later start up the system with that image file and be exactly where I was when I saved the image, right down to the positions and contents of all the open windows. I could save an image showing some bug or some strange behavior and give it to a colleague so that they could see it, too, and interact with the restored dynamic state to debug it.
I enjoyed comprehensive whole-system reflection that enabled me to view and edit absolutely everything in the running system while it ran. I could inspect absolutely everything, including the development environment and all its tools, interactively change any variable or field value, redefine any type or function, and continue to work with the changed system without stopping and restarting. (Obviously, if I made a bad change I might break the system, but remember, I could kill it and get back to where I started in a second or so).
I could start some process running--perhaps a 3D animation in a game, or a discrete-event simulation, or whatever--and change any values or definitions I liked to see what changed in the running process, without stopping the process to rebuild. For example, I could tell a rotating copper cube to become a glass icosahedron and reasonably expect to see my changes immediately reflected in the running program. This property is invaluable not only in games, simulations, and any kind of work with a visual-design component, but also in any kind of exploratory programming, where you're constructing data structures and evaluating expressions interactively to test your ideas.
Similarly, I could build some speculative data structure to explore an idea, and define some functions to operate on it. I could evaluate those expressions to see their results or to change the example structure. I could inspect the structure interactively and edit it in place if I think something different would work better. If I think a problem is caused by some structure or value in it, I could use the inspector to change it and see. If I thought one my my functions was doing something I didn't expect, I could insert a call to break, to activate a repl from inside the function call that would enable me to inspect and edit the data structure, redefine the function, and continue from there.
Anything the development system could do, I could do by typing an expression into the repl. As an example, nowadays you can still rebuild the whole Clozure Common Lisp environment from the ground up by typing (rebuild-ccl :full t).
The point is not that I would want to rebuld my Lisp from the repl all the time. The point is that the repl doesn't impose any aribtrary boundaries on what I can do. If the language and development environment can do it, I can do it from the repl. This is one of the properties that distinguishes the whole-system interactive design of these old tools from the more limited repls offered by newer ones. In pretty much every repl I've used other than old-style Lisps and Smalltalks I'm all the time stumbling over things you can't do from the repl.
I mentioned breakloops above. Their absence in younger languages and tools seem to me like some sort of sin, like we're tragically abandoning some of the best lessons of the past. Few newer development systems have them, but they're incredibly useful--at least if the language runtime is designed to properly support interactive programming.
A breakloop is a repl with all of the same affordances of the normal repl, but extended with all of the dynamic state of the control path that invoked the breakloop. If an error or an intentional call to break triggers a breakloop somewhere deep in a stack of recursive function calls, you get a repl that can see every frame of that stack, and every variable and value lexically accessible from it. You can browse all of that whole, change values, and redefine functions and types. You can resume execution at your leisure, and any changes you made in the breakloop will be visible in the resumed computation just as if that's how things were originally.
Proper breakloops don't just improve error messages; they replace them wholesale with an entire species of programming that lays the whole dynamic state of the system out on the table for you to examine and modify while the program continues to run.
Moreover, everything I just described about breakloops can also be automated. These old systems provide not only interactive tools for rummaging through the dynamic state of a suspended computation, but also APIs for handling them under program control. For example, you can wrap an arbitrary function call in condition handlers that will either drop you into a breakloop and enable you to vivisect the program state, or consult the dynamic state and compute which of several restarts to activate in order to transfer control to a path of your choosing.
I'm banging up against HN's length limit, but the above, I hope, goes some way toward answering to your question.
Very nice description of the power of the Common Lisp REPL.
Did you have the pleasure (?) of working on any of the lisp machines?
I got into Common Lisp in the early 90's and even then, with GCL and CMUCL, it blew my mind at how much more productive I was compared to my years of C,C++ just due to the one fact that I was now freed from the edit, compile, run cycle, let alone all the other benefits.
It's truly a joy. I don't have a full grasp of why others don't make the deep dive. I suspect it just had too much a learning curve nowadays, with Emacs being almost a requirement, and developers, like most humans, don't like to slow down for a while while they learn before they speed up.
I feel like I ran out of room before I really covered all that I wanted to cover, but maybe it's better that way. I wrote a tome, as it is. The market for multi-page screeds about how great ancient programming systems were may be limited.
I had a Symbolics Lisp Machine in my office at Apple for several years, and I noodled around with it a lot, but didn't build anything very serious with it. It's certainly relevant to any discussion of the power of full-bore repl-driven programming, though. Now that was a repl!
To be fair, I don't think everyone will find the advantages compelling. As far as I can see, some folks honestly prefer the programming-as-carpentry approach, and a skillful software carpenter can be extremely productive, too.
On the other hand, for someone who is, like me, more effective using the teaching approach, the difference is night and day. I have actually been one of those mythical 10X programmers before, as measured by version-control-system statistics, but only when I was working with a programming-as-teaching toolchain.
You touched upon a point that I think does work against Common Lisp nowadays: it's not super easy for someone to get started with it. I was lucky in the 1980s in that there was a very good Common Lisp on the Mac that was also really really easy to get started with.
Installing Coral Common Lisp was as simple as dragging a folder icon in the Finder. Starting up a usable IDE? Just double-click the app icon. Create a fully-working application window that handled standard events properly?
(make-instance 'window)
Build a GUI? You could either make-instance a bunch of objects and stick them into the window, or you could open a UI-builder window and drag stuff together. Tell the objects what events to handle and what functions to call, and you had a working app.
Deliver the app to production? Choose "save application" from the File menu.
The core of that same Lisp still exists in the form of Clozure Common Lisp, and it's very good, but it's not the same. For one thing, the IP agreement that open-sourced the compiler and runtime did not include the Mac integration. For another, there are a lot more hoops to jump through in UNIX-based macOS than in the old Mac system to achieve the kind of integration that Coral Lisp had. I'm not saying you couldn't do it; I'm just saying it would be a lot of work.
Actually, I think you could make a pretty good try at a Coral-like Lisp, and I think it would make things a lot easier and more attractive for new Lispers. It might even grow the pool of working Lispers, and improve awareness of programming-as-teaching. It would just be a lot of work, and it's not blindingly obvious that the result would pay for itself.
Probably the most straightforward route is to choose a system of this type—-either a Lisp or a Smalltalk implementation—-and learn how it behaves.
The source code for both Lisp and Smalltalk implementations is readily available, but may not help you all that much until you’re at least generally familiar with what the features are and what they do. Once you’re familiar with their behavior and with reading Lisp or Smalltalk code, then you can start rummaging through the code and asking questions of the implementors. Implementors of Lisps like CCL and sbcl, and of Smalltalks like Squeak and Pharo are pretty accessible and generally pretty friendly, and they certainly know how the systems work.
Consider the standard Common Lisp generic function UPDATE-INSTANCE-FOR-REDEFINED-CLASS (http://clhs.lisp.se/Body/f_upda_1.htm). It reinitializes an object when Lisp detects that the object's class definition has changed.
Ask yourself this: who would call such a function? Why would anyone ever invent it? Not only did someone invent it, a committee of some of the world's smartest and most experienced Lisp programmers wrote it into the ANSI standard for the language. What were they up to?
UPDATE-INSTANCE-FOR-REDEFINED-CLASS is not a weird anomaly; it's part of a carefully-considered set of features and protocols designed to support a specific style of programming. The Lisp runtime calls it for you automatically when it touches an object whose class definition has changed.
If you've defined a method specialized for it, then Lisp executes that method to rebuild the touched instance as if it had originally been instantiated from the class's new definition, and then your program goes its merry way. If you didn't specialize UPDATE-INSTANCE-FOR-REDEFINED-CLASS for this case, then the Lisp drops you into a breakloop.
A breakloop is an interactive repl with full access to all of the runtime's memory and all of the language's features, including visibility into the whole call stack that landed you in the breakloop. You can wander up and down the call stack, inspect anything in the runtime, edit bindings, redefine types and functions, and resume execution either at the point of control where the breakloop started, or at any other point for which the breakloop exposes a restart.
UPDATE-INSTANCE-FOR-REDEFINED-CLASS is not the weird fever dream of a confused eccentric. It's part of a purposeful system design intended to support a style of programming in which you build a program by interacting with a live runtime and teach it, interaction-by-interaction, how to be the program you want, while it runs.
It's a particular example of a general approach to programming best exemplified by these old systems. That general approach is the answer to your question: "Can someone knowledgeable explain how are lisp REPLs different from Python / Ruby REPLs? What is the differentiating point of REPL driven development?"
The differentiating point is that the entire language and system is thoughtfully designed from the ground up with the assumption that you're going to be changing your work in progress while it runs, and that you should be able to change absolutely anything about it as it runs and have a reasonable expectation that it will continue to work while you do it.
I like to call this style of programming "programming as teaching", and distinguish it from the much more widespread "programming as carpentry", in which the programmer is, metaphorically speaking, at a workbench banging together artifacts and assembling them to see how they turn out.
To be clear, I do not claim that the teaching approach is objectively better than the carpentry approach. I claim only that I, personally, am happier and measurably more productive using the teaching approach. I know that some other programmers report the same thing, and I suspect that if the teaching style of programming were more widely known, then there would be more programmers who prefer it.
There are several sibling comments that assert that any language can be made to support repl-driven programming, or that offer various languages and systems as examples of repl-driven programming. I'm sure that's all true, for some relatively restricted version of repl-driven programming, but the gold standard in repl-driven programming is programming as teaching in the style of old-fashioned Lisp and Smalltalk systems. These old systems offer amenities that the younger alternatives touted here do not match. I want more people to be aware of what they're missing.
Starting in the 1980s, I grew accustomed to systems that could start from cold in about a second, presenting to me a complete interactive development environment with all tools preloaded and ready to work, with the whole dynamic environment of my work in progress in the same state it was in the last time I was working with it. Moreover, I was accustomed to being able to take a single file from one machine to another to reproduce that same whole working environment equally quickly and easily on the new machine.
I could save the entire dynamic state of the running system to an image file, a serialized version of the running system's memory. I could later start up the system with that image file and be exactly where I was when I saved the image, right down to the positions and contents of all the open windows. I could save an image showing some bug or some strange behavior and give it to a colleague so that they could see it, too, and interact with the restored dynamic state to debug it.
I enjoyed comprehensive whole-system reflection that enabled me to view and edit absolutely everything in the running system while it ran. I could inspect absolutely everything, including the development environment and all its tools, interactively change any variable or field value, redefine any type or function, and continue to work with the changed system without stopping and restarting. (Obviously, if I made a bad change I might break the system, but remember, I could kill it and get back to where I started in a second or so).
I could start some process running--perhaps a 3D animation in a game, or a discrete-event simulation, or whatever--and change any values or definitions I liked to see what changed in the running process, without stopping the process to rebuild. For example, I could tell a rotating copper cube to become a glass icosahedron and reasonably expect to see my changes immediately reflected in the running program. This property is invaluable not only in games, simulations, and any kind of work with a visual-design component, but also in any kind of exploratory programming, where you're constructing data structures and evaluating expressions interactively to test your ideas.
Similarly, I could build some speculative data structure to explore an idea, and define some functions to operate on it. I could evaluate those expressions to see their results or to change the example structure. I could inspect the structure interactively and edit it in place if I think something different would work better. If I think a problem is caused by some structure or value in it, I could use the inspector to change it and see. If I thought one my my functions was doing something I didn't expect, I could insert a call to break, to activate a repl from inside the function call that would enable me to inspect and edit the data structure, redefine the function, and continue from there.
Anything the development system could do, I could do by typing an expression into the repl. As an example, nowadays you can still rebuild the whole Clozure Common Lisp environment from the ground up by typing (rebuild-ccl :full t).
The point is not that I would want to rebuld my Lisp from the repl all the time. The point is that the repl doesn't impose any aribtrary boundaries on what I can do. If the language and development environment can do it, I can do it from the repl. This is one of the properties that distinguishes the whole-system interactive design of these old tools from the more limited repls offered by newer ones. In pretty much every repl I've used other than old-style Lisps and Smalltalks I'm all the time stumbling over things you can't do from the repl.
I mentioned breakloops above. Their absence in younger languages and tools seem to me like some sort of sin, like we're tragically abandoning some of the best lessons of the past. Few newer development systems have them, but they're incredibly useful--at least if the language runtime is designed to properly support interactive programming.
A breakloop is a repl with all of the same affordances of the normal repl, but extended with all of the dynamic state of the control path that invoked the breakloop. If an error or an intentional call to break triggers a breakloop somewhere deep in a stack of recursive function calls, you get a repl that can see every frame of that stack, and every variable and value lexically accessible from it. You can browse all of that whole, change values, and redefine functions and types. You can resume execution at your leisure, and any changes you made in the breakloop will be visible in the resumed computation just as if that's how things were originally.
Proper breakloops don't just improve error messages; they replace them wholesale with an entire species of programming that lays the whole dynamic state of the system out on the table for you to examine and modify while the program continues to run.
Moreover, everything I just described about breakloops can also be automated. These old systems provide not only interactive tools for rummaging through the dynamic state of a suspended computation, but also APIs for handling them under program control. For example, you can wrap an arbitrary function call in condition handlers that will either drop you into a breakloop and enable you to vivisect the program state, or consult the dynamic state and compute which of several restarts to activate in order to transfer control to a path of your choosing.
I'm banging up against HN's length limit, but the above, I hope, goes some way toward answering to your question.