Curious why lisp's REPL is frequently touted as an incredible language feature e.g.:
> Support for this style of interactive development doesn't just come from some fancy editor plugins — it's baked into the bones of the language.
> So how do you actually get this wonderful interactive experience?
I've only ever programmed in interpreted languages (R, ruby), so I can't really understand how or why a REPL is so great since to me a (console|REPL|interpreter) is a standard feature (nothing extraordinary). Perhaps because I haven't had to work in a language without the convenient and immediate ability to execute arbitrary user inputs (as a REPL or interpreter can), for example a compiled language.
Had you worked in Fortran, Pascal or Algol or C, and been forced to think linearly to a deck of cards, and a job queue, and do all the marshalling of the IO into that job queue through some horrendous syntax of JCL of some kind, then the experience of being in a REPL might be more momentus. Instead you're a fish swimming in clear water not understanding why water is so unbelievably amazing if you haven't been in it before.
Of course LISPians want to make their REPL very meta, compared to any REPL, and it is: its degree of self-introspection, and the potential to modify the REPL is a REPL on steroids experience. But just being a REPL, is pretty damn amazing if you had to uplift from write-compile-assemble-marshall-coordinate-queue-run-cleanup "before"
(I did learn on punched cards in the 70s. Bugs hurt at a 20 min production-to-run cycle, some people were a drive away from the batch queue, and a 1 day turnaround was good.)
The REPL can be much more than a prompt where you execute code. In fact with many editors you can evaluate blocks of lisp code directly. Networked REPL's are amazing, specially when you're fixing code on machines that are difficult to physically access, like in space. The ability to query the current value of a symbol (function, macro, variable, record) and replace it at runtime without clobbering the runtime state is a great boon to the development process.
Also Common Lisp and a few Schemes allow you to interpret and compile a symbol's value (to speed up execution).
more like "batch compiled language". In many Lisps one would incrementally compile code. Common Lisp has already three functions built-in: LOAD, COMPILE and COMPILE-FILE. How they work is depending on the implementation, but LOAD typically can load source and compiled files, COMPILE compiles a single function in memory, and COMPILE-FILE compiles a source file to a compiled file (native machine code or some byte-code). SBCL for example compiles with these functions every thing to machine code, which it does anyway, by default.
One of the things which make this useable for incremental work is the symbol table, which holds all symbols and its values&functions. One can change the function of a symbol at runtime and global function calls go through this symbol table (-> late binding). So an update to a function has effect on its next call.
When using Python, the REPL does not feel remotely integral to the experience. For example, my Django project, when an exception occurs in debug mode, has a nice page with a backtrace et cetera. However, I cannot resume, or poke around using a REPL, or even view details of local variables. I feel like if Django was implemented in Common Lisp this would not be a problem at all, if not for technical reasons then for cultural reasons.
If you install the django-extensions package and use runserver_plus instead of runserver, when you hit an exception you get a live REPL in your browser where you can poke around and inspect at will. It's not CL-level power, but it's mighty useful.
For non Django code, the IPython REPL with the %pdb magic enabled drops you in a ipdb debugger on an exception. Doesn't allow resuming but still very useful.
The difference, as I understand it, is that the REPL is the running Lisp, and all of its code. You can inspect or redefine any function inside of Lisp itself, live, no matter how integral, in the same way you input any of your own code.
The interactive mode of interpreted languages is more of a sandbox, from that perspective.
The REPL often is also the default debugger. On error one gets a debug REPL at the point of the error (no unwinding), one debug level deeper. The debug REPL is basically a normal REPL, but with some debug features (stack movement, ...) and the ability to call predefined restarts in the code.
> Curious why lisp's REPL is frequently touted as an incredible language feature e.g.
Another language that’s great in that regard is Smalltalk/Pharo. It’s the Minecraft of programming languages. Your program start as an IDE and you morph it to what you want. Feels like building a car while it’s running.
Yes, touting the REPL of Lisps is a bit misleading. It's better to say that Lisps and Smalltalk(s) are deeply interactive programming experiences, in ways that most languages aren't yet.
It's not that misleading, though, true, both provide various interactive development environments, with Smalltalk versions very much GUI tool based (with tools like the System Browser).
Typically when one starts a Lisp development system, it will always start some kind of visible REPL. Either it runs it in a Terminal (most UNIX Lisps have a terminal REPL), runs it as a tool in its own GUI-based IDE (MCL, CCL, Allegro CL, LispWorks, Corman Lisp, ..., Symbolics Genera, CL + McCLIM) or runs the REPL frontend in a connected IDE (-> SLIME + GNU Emacs is a prominent IDE for CL, earlier this was done using subprocesses called "inferior Lisps" like in ILISP+GNU Emacs).
The conversational REPL interface (typing expressions, evaluating the expression and getting a response) is widely used.
Smalltalk has the "Workspace" or a Playground (-> Pharo) as similar tools.
Uhh, yes, I'm familiar. I write Clojure for a living.
What's misleading is touting the acronym "REPL", instead of touting why that's important. Everyone who's used to the weaker "REPLs" of other languages don't understand what the big deal is, because they think they already know, so it's important to move past the acronym when explaining its value.
I can think of a REPL use case I don't know how to do easily in another language. Doable yes, but easily and conveniently, no.
I want to build a little tool for the command line: meaning it should be runnable at any time I pull down my terminal, easily started and stopped. In it, I want to listen to a Telegram room. Basically the idea is, in this Telegram room, things are announced, and I want to look up some of them. It could be anything: sneakers, toys, concert tickets. But let's say crypto projects, as there is a running stream of them all day. (Doesn't have to be if you don't like crypto but let's go with it for now).
So I'm printing this list of crypto projects to the screen. Every so often I want to hover over one and get more information about it. For example, pretend there's a project called PGPalooza, one of many that's being printed in this list that's spilling out in real time on my terminal screen. I scroll up to PGPalooza and run a command on it, and then it runs some commands and opens up a few browser windows that I can look at to learn more about it.
This is easy to do in Lisp, in an intuitive natural way, that I don't think I could do with the same ease in other languages. You could think of this as demonstrating the benefits of interactivity in action. I can scroll up and modify the PGPalooza text to read lookup("PGPalooza") and then press Ctrl-E at the end to return one type of data, and financials("PGPalooza") to see another. It's also very easy to modularly add commands to it too, to expand on this.
What most answers here are missing is pointing out the unique difference between a LISP REPL and an interpreter console/shell kind of thing, which is that it fits seamlessly into the languages design. I often see Javascript devs quite unimpressed by a REPL - "I have a console, too". And while Javascript admittedly comes close, its just not the same.
What makes LISP REPLS special is precisely that there is nothing special about them.
There are no extra ceremonies, caveats, or layers that turn a LISP into a REPL. Its conceptually a fourliner, merely replacing the compilers input of parsing text files with reading user input, while providing access to a modified environment after each step. This is possible not because of the REPL, but because of how LISP is designed. It understands going into packages, importing stuff, inspecting values all naturally, not via some "interactive" mode / layer. You are speaking to the compiler directly and it understands your language. For this reason it is much more reliable and natural to embed a REPL in your development process in LISP, than in other languages.
> you can even modify library code, maybe even the runtime
this would be difficult (and not recommended) but not strictly impossible in languages other than lisp (e.g. read library files, edit them, re-write them, re-load the library).
But to play devil's advocate, isn't the language feature that makes this easy in lisp its homoiconicity, as opposed to its REPL?
* The environment for top-level definitions in the file is almost identical to REPL. I.e. there's not much special about a file, thus you don't need a context of a file to submit a new definition.
* Symbols provide an indirection mechanism which isn't tied to a specific location.
* CLOS actually put a lot of effort into doing something reasonable on redefinition.
Python module system is based on dicts. While it would seem like it would be easy to manipulate in runtime, module 's dicts are constructed by module-level import statements, and re-constructing the right state of all modules after an update might be tricky.
OTOH if you update symbol's function in CL, every user of that symbol will pick up the new definition. It's also more performant than dicts: caller has a pointer to a symbol and needs to do just one indirection, there's no lookup at runtime.
Another feature of CL, specifically CLOS, is that it actually has a formal protocol for changing classes.
A formal protocol you can tap into. While hardly omniscient, I don’t know of another language that does offer this. I don’t even think Smalltalk has this. (Smalltalk has a fundamental primitive related to this called “become:”, which can be used for this purpose, but that’s less formal than what CLOS provides.)
What does this mean? It means that you can change the structure of classes, AND their instances, in a live, running system. Not just their structure, but how the system transmutes from the old structure to the new structure. How the conversion is done.
What does this have to do with the REPL? It’s part and parcel of the kind of environment and functionality of the system that is exposed by the REPL.
The REPL is not just a console that you can type into, and use backspace, and what not. It’s the door to the very rich world underlying the system.
And this is the key point. In CL, the REPL is not an afterthought. It’s not an add-on. It’s a core competency. Much of this no doubt came from the Lisp Machine experience, where all you had was a running image that, like a surgeon, you had your arms elbow deep into. A system where you could not trivially just stop and restart for every little change. A system where you had to have the ability to change the tires on a running vehicle.
The vast majority of modern REPL environments don’t have that burden, so when things get tough, they can punt. “Eh, just restart and do over.”
It’s absolutely fair to consider whether that quality is actually still germane in the modern era. Hard to imagine a scenario where you might do something like a change like this on a running server, especially in our age of “cattle, not pets”.
But the legacy is still there from that past time. A time when not only were they pets, they were coddled and spoiled. So the folks back then had to think this stuff through.
That is why I always give Lisp enviroments and Smalltalk as example, when someone comes up with the common excuse that Python cannot have good JITs, as it is too dynamic.
If AWS had an R&D Labs division with zero expectation of profit I think you could sell them an idea of Elastic Lisp Machine where a deployment is not a restart but whatever you need to do in the lisp world. (Patch an image…? No idea but sounds cool!)
The REPL is not an add-on in the sense that it comes with the implementation, but it is in the sense that if it were not provided, it could be implemented using only things available standardly in a Common Lisp implementation.
The language features that make this possible are a) late-binding and b) resident development tools (compiler, debugger, interpreter, inspector, trace, ... are all included in the runtime).
Not all kinds of late-binding is good. Python uses dicts which has more runtime overhead than symbols, but make it hard to develop code interactively. Consider
foo.py
def foo1(x): return x+1
bar.py
from foo import foo1
def bar1(x): return foo1(x)\*2
I can modify foo.foo1 in REPL:
foo.foo1 = lambda x: x+3
but bar.bar1 will still use the old definition!
bar1 lookups foo1 via a dict which is constructed when bar is imported, so it won't be updated unless I reload all files.
So dicts are just wrong for this, there are too many of them. Symbols are basically perfect for this as they provide an easily manageable point of indirection and also much more performant.
Not really. The runtime, plus your code is a virtual machine. It’s similar to docker container in that regard. The REPL is your shell. And you can poke around everything. Another language that has that is Smalltalk/pharo. In comparison, the Python REPL feel like the os part was burned on ROM.
An important thing to remember is that both R and Ruby have directly, wholly, imported the REPL approach (not just having an interpreter you can type at, like Python) from Lisp/Common Lisp - with R arguably fitting as Lisp-family language if with different syntax (Ruby's other inspirations were Smalltalk obviously, and less known was being Perl replacement)
It's blindingly obvious to me that Ruby's basically "put smalltalk and perl in a blender with a tab of good acid" (and I mean that as a compliment).
The main reasons I bounced off ruby and stayed with perl were
- variables popping into existence on first assignment
- lack of block based scoping (i.e. perl's 'my', or ES6 'let')
- the OO model is ... restrictive ... and trickier to bend [1]
- less amenable to syntax plugins (e.g. you can implement async/await as a library in perl, not so much in other things)
I'd note that Elixir kinda manages to steal good parts from both ruby -and- perl and I find it much more comfortable.
Also that other than the two scoping related ones, my complaint mostly boils down to "can't bend the language in insane ways" and it's fair to think that letting people do that is more trouble than it's worth.
(i.e. please take this comment in a spirit of being why -I- ended up still preferring perl, rather than a claim that anybody else should ... but after 'let' in lisp and 'my' in perl, I'm spoiled for any scoping model that doesn't do that ;)
[1] single inheritance would be survivable if it had based itself on a trait-based smalltalk but modules are ... not really enough, and the achievable aesthetics when building stuff on top yourself aren't quite my thing ... this is my fuzziest claim and you're welcome to file it under 'personal taste'
There's a lot of language semantics which make the REPL convenient to write interesting amounts of code in, Gilad Bracha details some in <https://blog.bracha.org/primordialsoup.html?snapshot=Amplefo...> (with the message reflected in the medium, embedding the Newspeak IDE in the document for examples). It's unrelated to compiling or interpreting; many Common Lisp implementations will compile code from the REPL too.
Curious why lisp's REPL is frequently touted as an incredible language feature e.g.:
> Support for this style of interactive development doesn't just come from some fancy editor plugins — it's baked into the bones of the language.
> So how do you actually get this wonderful interactive experience?
I've only ever programmed in interpreted languages (R, ruby), so I can't really understand how or why a REPL is so great since to me a (console|REPL|interpreter) is a standard feature (nothing extraordinary). Perhaps because I haven't had to work in a language without the convenient and immediate ability to execute arbitrary user inputs (as a REPL or interpreter can), for example a compiled language.