REPL Debugging: No Stacktrace Required (2017)
blog.cognitect.com
blog.cognitect.com
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!
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.
I could just have the compiler explain the problem in the REPL, for example with SBCL:
* (defun foo (n)
(cond ((> n 40) (+ n 20))
((> n 20) (- (first n) 20))
(t 0)))
; in: DEFUN FOO
; (FIRST N)
; ==>
; (CAR N)
;
; caught WARNING:
; Derived type of N is
; (VALUES
; (OR (DOUBLE-FLOAT (20.0d0) 40.0d0) (SINGLE-FLOAT (20.0) 40.0)
; (RATIONAL (20) 40))
; &OPTIONAL),
;
; conflicting with its asserted type
; LIST.
Then there is less need for manually searching for problematic expressions in the code.See http://sbcl.org
> I am going to throw away all the potentially useful information
> 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.
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.
Most REPLs I've used in other languages are strictly for evaluating individual commands in an isolated, stateless manner.
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.
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.
That would be surprising, since the REPL was developed for Lisp in the 60s, which actually works that way.
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.
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.
Usually Ruby is used with Rails, where in development mode modifying the source and repeating the request is just as easy.
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.
If you want the full blown experience try out the community versions of LispWorks or Allegro Common Lisp.
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.
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.
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.
Fix and continue. Various language implementations provide that.
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 find that quite surprising, as I use the REPL a lot when coding in Python.
Monster lists 68000 positions after a search for 'Java', and 180 for 'Clojure'.
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...
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
https://lambdaisland.com/blog/2020-02-03-open-collective-lam...
https://www.clojuriststogether.org/
Finally, Clojure usage is growing for new projects both in enterprise and government sectors in countries like Finalnd
https://twitter.com/autiomaa/status/1235944730267193345
https://docs.google.com/presentation/d/1nCZ-GmWLcH8Dcz3XJHY0...
This is a very different picture from a tiny isolated community of a few thousand developers that you seem to be painting.
This says nothing about the number of human users.
> This is a very different picture from a tiny isolated community of a few thousand developers that you seem to be painting.
https://opencollective.com/lambda-island#section-contributor...
They have 13 contributors.
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/
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.
> Wonder why you didn't mention anything about Clojurists Together, doesn't fit your narrative? https://www.clojuriststogether.org/members/
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).
Mainstream: large relative and absolute numbers of users.
For example the Porsche Taycan (-> Microsoft) is not a mainstream car, even though Bill Gates has one and Porsche is an incredible profitable company. https://www.economist.com/business/2019/09/12/porsche-is-sma...
I thought being in the top is the definition of 'mainstream': 'most people use it, know it, learn it, ...'.
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.
Programmers are equally productive[0] (or way more because of their big ecosystems) in languages that have a fast code-run cycle.
[0] https://groups.google.com/forum/#!topic/comp.lang.lisp/L5dZ-...
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.
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.
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
In Clojure that's supported via a function call
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.