It'd be pretty simple — albeit so very much a bad idea — to run the REPL from a stream which saved everything typed in the REPL to a log read in at startup. A fancier version might allow log editing. A really fancy version might save everything to a log and offer commands to view expressions in the log & copy the desired ones to the startup file, or another file.
Or one could just remember to move corrected source back to the right place.
This is probably somewhere where a Smalltalk image-based browser would be preferable — it would/could unify the REPL and editing source.
You would drive the REPL from the editor. Change the code in the editor and evaluate into the REPL. Still you need to try out if the error in a fresh build is still fixed after that.
> read stacktraces and add instrumentation for tracing. In this blog post I am going to debug his example program without using such tooling, relying only on the rapid feedback loop provided by the REPL
One does not need error reporting, stacktraces or add instrumentation, while running the code. SBCL gives the clue without any of that, before (!) the code runs. In the REPL. The incremental compiler makes some of that manually searching for type problems in the code unnecessary, while supporting REPL use.
I was talking with a Data Scientist friend abiut what I love so much about Clojure and it was largely about the REPL. To him it sounded like nothing special - trying expressions in a prompt is hardly new or original and he does all the time in R. Which I thought is actually a really good selling point for Clojure - you get to do the really high-level data manipulation you do in R, Mathematica or even Python in a language that is compiled, good with concurrency, probably embraces your platform of choice and is also used for the frontend.
What makes it different for me is two things:
- the functional style mean that many things are much easier to track in a REPL - you don't need to use a stepping debugger to get to the troublesome value of i as often.
- Structural editing using parinfer or similar is fun.
Overall I'm not convinced that the Clojure REPL is that unique, but combined with the rest of the ecosystem it is pretty sweet.
I think the major difference between the Clojure REPL experience and most other language's REPLs that I've worked with is the fact that Clojure REPLs can connect to a running stateful system (or a situated program as Rich Hickey coined it), and allows you to explore and manipulate the system's state and behavior dynamically at runtime, which makes it an incredibly powerful tool for building and debugging such a system.
Most REPLs I've used in other languages are strictly for evaluating individual commands in an isolated, stateless manner.
I like how you just stated "this is a native killer feature", and immediately, 2 other comments stated, "well, with a bit of work, we can have half of this killer feature too in my language".
Come on, I'm a Python fan, but no, we don't have anything close to that Clojure experience. And it's certainly not native anyway.
I mean, twisted? Really? I co-wrote the book "Expert Twisted", and even I wouldn't be caught dead pretending it's simple. So how can you advocate to do this + ssh + reload() (which is very unreliable in python) as an alternative to what OP talks about.
No language is perfect. Other techs have fascinating promises. Let's stop a moment and admire that instead of trying to sell our stuff all the time.
I think immutability plays a big role here as well. Clojure makes it natural to structure your code in a way where things can be reloaded individually without having to worry about global state. The REPL would be of much more limited use in a language where everything is coupled via shared mutable references.
This can also be seen with ClojureScript where hot loading works reliably out of the box, while all attempts at do hot loading in Js are flaky to the point of not being of much use.
In Python, twisted.conch provides an ssh transport and a repl implementation which is trivial to hook up in a live system. The ecosystem doesn't really support the use case though, so you end up building all tooling from scratch (tmux plus vimux helps a lot here). You also have to structure your code in a somewhat unnatural way for python, like using a dependency injection system to reload and trying to approximate clojure.core/in-ns.
Connecting through ssh to the machine and running a Python debugger there is a much better supported alternative that will give you a REPL capable of querying and modifying a running system.
Also, you can avoid dependency injection with monkey patching.
Lisp will give you a 2 way compiler, where you can change things and then save them into a source file, AFAIK, Python has nothing like this, but just changing a live system is well supported and common.
> Connecting through ssh to the machine and running a Python debugger there is a much better supported alternative that will give you a REPL capable of querying and modifying a running system.
ssh into an interpreter gives me perfect control and is reasonably well supported (as far as twisted things go). If you have tmux setup correctly, you can use emacs/vim to eval against your ssh'd python the same way you'd work locally. I've done this for over a decade. It's similar to how I use nrepl+clojure.
You can't avoid dependency injection with monkey patching. You have to restart your system on certain changes or reload all of your modules. Further, you need a global reference as an entrypoint to your objects, otherwise you drop in with a debugger/repl and you, what exactly? set a breakpoint for the entire running application? No thanks, not in prod anyway.
At least in Clojure, our best practices including using live REPLs for debugging/troubleshooting, but patching only when absolutely necessary. At my last gig, we had tooling setup to allow varying levels of modifications to a particular clojure app with full traffic, limited traffic, or no traffic (depending on what type of environment was required for debugging), and then tooling to auto-apply patches via a combination of zk+ssh+nrepl (only listens on localhost; the hosts and ports were defined in zk) for emergencies (when the system couldn't be restarted safely for some reason). Most of the time, however, the tooling would give you a session to a given app, and when you left the session, would restart the process (safely via load balancing), since it would be problematic to leave systems in indeterminate state.
Ruby's REPL, and particularly when enhanced with Pry (binding.pry), can function like this reasonably trivially. You can't connect to an existing system like attaching a debugger, but you can make a call to binding.pry or binding.pry_remote depending on a parameter.
Usually Ruby is used with Rails, where in development mode modifying the source and repeating the request is just as easy.
This is cultural tbh, Lisp went all in with repl style and added more features over time. Give it 10 years and jshell or python will have similar traits (possibly).
My company has a lot of Clojure code but also supports users in R and Python, many of whom code notebook-first. One of the big differences that I have always observed is that Clojure (and other lisps possibly moreso) allow you to use the REPL alongside a living app and codebase. You can interactively develop small chunks of functionality and then integrate them into the whole.
Notebooks, however, impose this sort of top-down mental model that in my experience (supporting moving models to production, working with code form academia etc) very rarely leaves you a clear path to a maintainable codebase. Things like nbdev[0] might help a bit with deployment and refactoring, but it still feels like a completely different approach. Fundamentally a lot of people create notebooks as self-contained analysis that runs from top to bottom, first obtaining and preparing data, then trying to model the question at hand, and finally presenting results. The model is generally averse to ever scrolling back up. This doesn't feel at all like REPL-based development, where you're experimenting on one small part of a wider web of code, but the REPL history itself is much more transient and throwaway. There certainly can be a core loop in analysis where you're inspecting data, learning the patterns and distributions etc, but that generally yields insights, not code.
I assume that this isn't universally true, and many use R/Python REPLs to support more of a code-first approach, but I haven't observed that to be common.
Also, in Clojure you can easily evaluate sub-expressions, as Stu mentions in the article, which is not always possible in other languages interactive environments.
REPL is a killer Clojure feature that nobody (outside the ecosystem) talks about.
Probably it's hard to comprehend what developing in a REPL feels like, majorly because no other commercial language has a REPL as powerful as Clojure.
I gave a talk[1] explaining the REPL and most people in the audience (including senior Java, C# and Python developers) had never seen something like that before.
REPL is the reason why I (and perhaps other Clojure devs) endure the pain around Clojure tooling, demand, supply and ecosystem in general.
Everytime I've singled out the REPL as why I like Clojure so much when explaining why Clojure so great to my friends they seem to mention that other languages have repls too. Like F# or JavaScript. When I counter that Clojure's REPL is connected to the running app instance they are developing I don't think they really understand. Perhaps this is one of those things that you just have to try out to grok.
As is javascript.
Have you ever tried debugging an application with Chrome ?
I frequently use the "pause on exception" feature of the debugger or also place breakpoints strategically and then use the console as REPL for further debugging.
I think the "_running_ app instance" is the key distinction here.
Breakpoints require halting your system, so while you can inspect the system's state, you can't meaningfully alter it as you could with the kinds of Clojure REPL workflows talked about here.
Another limitation is that you can't really inspect parts of your system outside the context of the currently running function where the breakpoint was triggered.
I think debugging with state-preserving hot reloading in UI development is probably the closest analogue I've encountered to debugging workflows with a Clojure REPL, though it's still nowhere near as flexible.
> Breakpoints require halting your system, so while you can inspect the system's state, you can't meaningfully alter it as you could with the kinds of Clojure REPL workflows talked about here.
Fix and continue. Various language implementations provide that.
When I started learning/using Clojure, I didn't use the REPL much and kept on working in the traditional way: Write code, compile, test. Repeat until it works.
It took some time to break my old habits and start using the REPL. It's unfortunately made me a bit lazier writing unit tests :), as I can hammer on a piece of code until I know it works - and small, deterministic functions tend to not break once you get them working. So much of my test code is integration tests.
Nowadays, when I have to work in Java (or Scala, Javascript, Python, etc), I seriously miss the Clojure REPL. Though Clojure (and Lisp in general) has definitely affected the way I write code in other languages.
> I gave a talk[1] explaining the REPL and most people in the audience (including senior Java, C# and Python developers) had never seen something like that before.
I find that quite surprising, as I use the REPL a lot when coding in Python.
Even then the Python REPL can be quite useful but I do not expect most Python developers to use it. (This is from personal observations as a Common Lisp enthousiast.)
Seeing how Clojure is actively used at places like Apple, Amazon, Atlassian, and even Walmart where software development isn't even their primary business, I think it's pretty safe to call it mainstream.
I would call any language that's actively used in the industry with a large and active community mainstream. Restricting the term mainstream to a handful of most used languages is pretty silly in my opinion.
It's just that the community is not large. It's small. To think that 'a few thousand people' is large among millions of other programmers is 'silly' (to quote you).
The last Clojure survey has ~2500 people responding. Which is basically the same for several years now. I we assume that 10 times more people are actively using Clojure, then that would be 25000. Still a lot less than the probably 7 million Java programmers and 8 million Python developers. https://www.zdnet.com/article/programming-languages-python-d...
I'm not arguing that Clojure is as widely used as Java or Python, which are two most popular languages in the world. I'm saying that it's a ridiculous bar to set for calling something mainstream.
However, there are some other metrics we can look at. For example, if we look at downloads for popular libraries, we can see that Ring's been downloaded 9,058,741 times, and obviously there are repeat downloads for the same users, but even accounting for that it indicates a much bigger user base than you're suggesting. https://clojars.org/ring/ring-core
Other data points would be commercial tooling like Cursive and commercial funding for projects with organizations like Clojurists Together indicates that there is a critical mass of commercial users who are willing to fund projects
>This says nothing about the number of human users.
It obviously does because people are using these libraries in their projects. As I noted, it's an indirect measure, but it still gives an idea of the usage. And Open Collective was just announced last month, so not sure what point you were trying to make there. Wonder why you didn't mention anything about Clojurists Together, doesn't fit your narrative? https://www.clojuriststogether.org/members/
> It obviously does because people are using these libraries in their projects.
Obviously it says nothing about the number. If we look at the number of downloads of the current version, it's ~40000. Now many systems may download it multiple times. So the number is not an indicator for a large user community.
It 'fits my narrative' well. Just count the number of the members.
Don't get me wrong, that's all fine and useful in a small community, but to claim a handful of companies and a few hundred people is an indicator of a mainstream popularity is just massive overselling and looks 'silly' (to quote you again, sorry).
If commercial usage of the language by companies big and small around the world along with funding of the ecosystem is not an indication of mainstream usage, then I guess we'll have to disagree on what constitutes mainstream.
Right, a few users don't make something mainstream. Big companies have thousands of developers - even non IT companies - that someone inside some big company uses a programming language doesn't make it an indicator for widespread (most people use something -> mainstream) usage.
Mainstream: large relative and absolute numbers of users.
My whole point is that languages like like Js or Java are not a good metric, and you don't have to be as popular as the top handful of languages to be mainstream.
Mainstream is typically taken to be known and widely used which Clojure is. You seem to have a personal much more restrictive definition which only includes the top few languages for reasons unstated.
> Mainstream is typically taken to be known and widely used which Clojure is.
But Clojure is neither widely known nor widely used. The last Clojure survey was done by just 2500 people.
> You seem to have a personal much more restrictive definition which only includes the top few languages for reasons unstated.
The top ten languages are all two orders of magnitude more used than Clojure. They are mainstream. Literally they have millions of users. That's the definition of mainstream. Mainstream pop: the music most people hear and which dominates the charts. No one would claim that free jazz is mainstream, even though it has some stable fan group.
For a language that's not widely known it's weird how Clojure features in Stack Overflow and JVM surveys. Meanwhile, it's also strange that a language that's not widely used finds its ways into all kinds of well known companies.
I'll take your word on that. However, that doesn't change my point that Clojure is used at lost of large companies, and the recent developer survey showed that it's becoming more widely used at large companies.
A good REPL is nice, but its hardly a killer feature or a big productivity boost. If Clojure didn't have a REPL it would have been completely dead by now cause its startup time is really bad. Clojure needs to have a REPL in order to survive (which is barely doing).
Programmers are equally productive[0] (or way more because of their big ecosystems) in languages that have a fast code-run cycle.
REPL driven workflow allows me to put the application in a specific state, and then continue developing features against that state. If you have to restart the application each time you make a code change then you have to rebuild that state each time. This creates a much slower feedback loop, especially when you're working on large applications. Furthermore, the REPL is a fantastic tool for troubleshooting live systems in production, and in some cases even patching them with zero downtime.
I don't know a single person who's worked with this workflow on a serious project and would want to go back to compile/run cycle afterwards.
Meanwhile, Clojure leverages host ecosystem getting all the same benefits as any other language running on it.
>Programmers are equally productive[0] (or way more because of their big ecosystems) in languages that have a fast code-run cycle.
I would disagree with this. I feel much more productive in Lisp than, e.g. go or C or C++. In Smalltalk, the productivity was so great that I was dead tired after 8 hours, as it was all thinking and effectively zero compiling.
When talking about the Clojure REPL I think a lot of people forget that the language matters a lot
Clojure focuses on being dynamic, to give an example if in PHP you wanted to attach an interface to a class without restarting the REPL I don't think you could
I think this is about equivalent to the error messages one already gets from a typical Clojure REPL session, so no loss of potentially useful information, after all.
Sometimes Steve would do that to add a new feature or change some broken code.
The danger was that if you didn’t copy/paste the fix back to the saved source, the problem would return when the app restarted!