Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

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.


> The REPL would be of much more limited use in a language where everything is coupled via shared mutable references.

That would be surprising, since the REPL was developed for Lisp in the 60s, which actually works that way.


That isn't a contradiction - the advantages of a REPL are greater for Clojure than for Lisp.


From what I see that seems not to be the case. Reloading code in REPLs in Lisp is widely used.


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.

0: https://github.com/fastai/nbdev


Basically the whole Jupyter experience is reminiscent of how Lisp Machines and Smalltalk work.

If you want the full blown experience try out the community versions of LispWorks or Allegro Common Lisp.


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.


Basically every Lisp does that, but with the added bonus that one evaluates directly in the error context.


Sure, and in CL is even better. My point was that other languages (non-lisps, such as Python, Ruby, etc) don't allow for that.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: