Read: In Python, the input is read as strings of code, which are then parsed and compiled. In Lisp, the input is read as data structures (lists, symbols, etc.) due to its homoiconic nature (code is data). This allows for macros and syntax rules to be applied even before evaluation.
Eval - The evaluation phase involves interpreting the compiled code. Python’s evaluation is less interactive; changes in function definitions or classes often require the code to be re-run entirely, and the state can become inconsistent without careful management. Evaluation in Lisp happens directly on the read data structures. Functions, macros, and variables can be redefined interactively without restarting the REPL, maintaining a more fluid development process.
Print - The result of the evaluated Python code is printed out in a standard format. The printing is straightforward but lacks customization unless explicitly coded. In Lisp, results are printed in a way that preserves the structural representation of the data, facilitating easier inspection and manipulation in subsequent steps.
Loop - In Python, the main loop doesn’t offer much in terms of tooling or integration. Extending the REPL with custom behaviors or tools requires additional packages or integrations. The Lisp loop often includes advanced features like hooking into debugging tools, live editing, and runtime modifications, providing a richer development experience.
So, it really makes you ask: can you actually have a proper, "true" REPL in a non-Lisp language? So while it's fair to call them REPLs in a technical sense, they more like "interactive shells" and not REPLs. You really need a Lisp to have a proper REPL.
There is no comparison between the Clojure REPL and the Python REPL. Python's is just literally a shell that you connect to and can run commands in. The Clojure REPL is far closer to a Python Jupyter notebook (if you know about it) built into your editor - it's a long-running process to which you can send commands all the time. But because of the way lisp is structured, and because of the tooling, you can just write Python code in the file you would normally use, stand on one line, and hit a command to get it sent to the REPL and the output of it written in the editor.
It's... hard to explain how powerful this is, and how much better things could be if we had similar tooling in Python.
Note: I still much prefer Python for many reasons, but this one aspect is something that I also used to think (yeah, Python also has a REPL) and I realize now that I had no idea what power this really has in lisp-world.
So, let's take a minimal, simple example:
if True:
print("Hello, Python!")
and the equivalent code in Clojure: (when true (println "Hello, Clojure!"))
In Clojure, you can send any expression fragment like a single symbol, list or even incomplete code and it will be evaluated according to its context. So, you can sent `true`, you can send `println` and you can even send `(when true println)` or `(when true (println))`, or even `(when true ,,,, (println))` (you can replace commas with line breaks if you want) and it will still be evaluated.In Python, you can send `True`, and you can send `print`, but you can't send `if True: print` (even with corrected indentation), because Reader would have hard time making sense of that.
In Python, you need to adhere strictly to its syntax, and partial expressions that don't form a complete statement won't be evaluated meaningfully. In contrast, Clojure's REPL can work seamlessly with code fragments because the underlying representation of code is just data structures that are always valid within the REPL context.
What makes it mind-blowingly cool is that you can send these expressions to a remote REPL without any transformation, serialization, or encoding, because it's not just code — it is data. In fact, some teams do just that. They'll keep an open socket on a running app, possibly performing somewhere in the cloud, so they can interact with it in real time. Putting aside the notion that this might not be the greatest idea, let's agree that this is a cool feature. The famous example of that is when something broke on Deep Space 1 (DS1) spacecraft in 1998, the team was able to fix it via REPL, see https://www.youtube.com/watch?v=_gZK0tW8EhQ - "The Remote Agent Experiment: Debugging Code from 60 Million Miles Away"
Sure, yes, Python is great, but when it comes to REPL stuff, you simply need a Lisp. It really is an amazing feeling when you can, for example, poke into any HTML element on a page dynamically, directly from your editor. Sure, you can do that with devtools in the browser, but it's not the same. You still have to switch to the browser; you can't type statements that are too long; and it's just not very convenient.
Very nice (now years old) example is when Bruce Hauman showcased developing a "flappy-bird" clone - he would tweak the params, or straight change the code in his editor and the bird in the browser would fly differently, it's literally like writing code while playing a video game. Or Sam Aaron, writing music with Overtone is another great example. Another, more practical example is when you're exploring API response results, if you have REPL - you can send a single request and then dig through the data - group it, slice it, dice it, sort it, etc., something seemingly simple like this would require a bit more effort with any other language. I have my editor connected to a Clojure REPL instance at all times; I never know when I might need it.
[...]
> It's... hard to explain how powerful this is, and how much better things could be if we had similar tooling in Python.
It would be kind of odd if we didn't, given what ecosystem Jupyter comes from. Sure, it's kind of surprising that the editor to integrations to do this haven't been around for a long time, but...
https://code.visualstudio.com/docs/python/jupyter-support-py
Look, I've been using Python professional for 16 years, and ever since starting to use Jupyter 7 years ago, have been a huge fan and proponent of Jupyter. But the Jupter model is different from the Clojure "true REPL" model.
The main difference is that in Jupyter, you're doing something that is outside the normal files you use to write code. Both by writing in cells, but also you're physically in notebook files which are not part of a "regular codebase". Sure, if you're doing standalone analysis work or something, that makes a lot of sense. But if you're, say, writing the BE of a webapp, then you'd have your normal Django or whatever .py files, and separate Jupyter files for one-off analysis, or for writing testing code, etc.
In Clojure, those can be unified in a way, both in the development process and in the future. Meaning, you'd start off with the equivalent of your Django .py file, you'd make a special "smart comment" which doesn't get executed normally, but which you can manually execute via an IDE command, you'd write some code there, and once it's working, you'd move it outside the comment to the "regular" part of the file. You've now essentially iteratively developed a new function that is just part of your normal codebase.
And the "comment" that you wrote all the testing code in? That comment is still part of your file. You can use it to document the way your code runs, but in an interactive manner - anybody chancing on this file can just execute commands out of that comment and immediately see what they do.
It's... hard to explain, but it's a really powerful model, that completely obliterates the line between "exploratory" code like writing in Jupyter, and the "production" code that ends up in your actual codebase.
If you've never seen it, I highly recommend you look for a good video example of Clojure REPL-driven development.
That's not the case here (it works in normal .py files.)
> In Clojure, those can be unified in a way, both in the development process and in the future. Meaning, you'd start off with the equivalent of your Django .py file, you'd make a special "smart comment" which doesn't get executed normally, but which you can manually execute via an IDE command, you'd write some code there, and once it's working, you'd move it outside the comment to the "regular" part of the file. You've now essentially iteratively developed a new function that is just part of your normal codebase.
Yes, that exactly will work in the VSCode Interactive Python environment and is what distinguishes from normal Jupyter notebooks even though it relies on Jupyter under the hood (=the code can't actually be in a comment if you want to use the integration to send it to the interactive environment, because Python only has single line comments so sending it to the interactive environment would literally send a comment, but it can be in guarded-as-unreachable code -- it could also be in a triple-quoted string, which can work like a comment, but then you'll lose syntax highlighting, completion, etc., while writing it.)
One thing I'm skeptical about is whether the way Clojure itself is written, with lisp-style S-expressions that are identical in look, is an inherent advantage for this kind of work over the more standard way Python works. I'm curious to find out.