I recently started to work on a node library based on ClojureScript. My setup was Shadow-ClojureScript with Visual Studio Code + Calva that I connected to a node.js REPL. I was wondering that evaluating query statements to an MySql database simply worked.
Can someone explain to me why other languages don’t provide such a REPL support? In the case of ClojureScript, shouldn’t the code has to be compiled to node.js anyway? Why isn’t it possible then to just do the same for node?
With Lisp-based languages, the “unit of compilation” is a single, top-level form. Once the REPL has read in a single, well-formed “expression”, it can immediately compile it and then run it. The overall compilation and execution model for Clojure (and ClojureScript – with some caveats) is identical between the REPL and the “normal” command-line execution approach: read a form, compile it, execute it (and, in the REPL, print the result).
With nearly all other languages, they are typically designed so the “unit of compilation” is a file (or module) and then if they have an interactive console for the language, it’s often a separate interpreter allowing for line-by-line (or statement-by-statement) reading and evaluation.
For some languages, the interactive console reuses some of the same machinery that “normal” compilation/execution uses (for primarily interpreted languages). For compiled languages, the interactive console is a separate thing that often has some semantic differences and some limitations.
There’s a transcript of Stu Halloway’s excellent REPL-Driven Development talk that touches on the differences between a “shell” (interactive console) and Clojure’s REPL, about six minutes in: talk-transcripts/REPLDrivenDevelopment.md at master · matthiasn/talk-transcripts (github.com)
One outlier worth giving a closer look is Erlang. It’s not a LISP. Its base unit of compilation is a file. Yet it has a shell allowing you to evaluate arbitrary expressions, connect to a remote system, inspect its state, and reload the code it’s running without interrupting its users.
There’s also Clojerl with its own REPL.
I think that it’s common for languages that were inspired by Lisp to have REPLs. That’s true of the family tree descended from the original ML, which includes Standard ML, OCaml and Haskell. I don’t know the history of Erlang, but I wouldn’t be surprised if it was partly inspired by early Lisps.
ML et al don’t have REPLs in the Lisp sense, i.e., part of the live, running application context. They have interactive consoles – they may just have closer semantic matches between interpreted (shell) and compiled code than most languages.
Thanks @seancorfield–that’s interesting. I’m most familiar with OCaml at this point. I wasn’t aware of semantic differences in the REPL, but that doesn’t conflict with what you said. OCaml executables can be built either as bytecode or machine-specific binaries, but again, that doesn’t conflict with the point.
That made me curious about OCaml so I looked at the docs: OCaml - The toplevel system or REPL (ocaml).
The semantics for module handling are different – user modules have to be explicitly loaded via a toplevel-only directive – and it is explicitly a bytecode interpreter (but there’s an experimental native toplevel system being worked on too). There are some other toplevel-only directives and semantics too.
It’s interesting that you can make your own toplevel system, with your own user modules preloaded, which is similar to how you can take any Clojure uberjar and still run it as a REPL (
java -cp path/to/the.jar clojure.main) – although in OCaml this is still going to be a bytecode interpreter that can run scripts, whereas in Clojure it can be a fully-compiled application that still includes a REPL with full compilation semantics.
Adding to what others said, I also think that lack of dynamism and the syntax/semantics are reasons why other languages don’t have a REPL.
For a REPL to be useful, it can’t simply execute new code and show you the result. You also need to be able to inspect existing code, and modify existing code to do something else. That’s what I call dynamism. If we take Java for example, adding a method to an existing class at runtime isn’t possible, that’s already a huge barrier to making the Repl useful, since that would be one of the first things you’d want to do when using REPL driven development, you’d want to run the program and slowly modify and add to it’s behavior as it is running, and if you don’t have that level of dynamism available in your language/runtime to do so, then it’s kind of useless to have a REPL, and that’s when I’d call it a simple shell or command line instead.
I mentioned inspecting code already, but let me give an example here as well, you really want the REPL to auto-complete and show you the doc for various functions and global vars, that’s basic introspection, but it means the runtime must have first class doc-string and keep them around along the functions and the global vars, which again, isn’t something all languages do, like in Java, you need access to source files and you have to parse them to get the comment block above the corresponding methods to retrieve their doc, so it’s missing basic REPL introspection. Without that, again, a REPL is much less useful.
Finally, I believe syntax and semantics is a big one as well. The Lisp syntax makes it so every chunk of executable code is nicely bounded so it is easy to select and send to the REPL for evaluation, you don’t have to highlight or do anything else, just form under cursor, or form before cursor and you can send it to the REPL, that’s really nice ergonomics right there. Especially combined with the quick navigation it gives you, moving to the previous or next form, moving up to the parent form or down to child form, its all super quick and easy to navigate. And then the scoping rules, which itself is form bound, and not like at the class or the method level only, so if you want to evaluate a part of a function on its own, you can do that and capture some of the locals within it.
I think those things is what really makes the REPL driven development useful and productive, so the language needs to support these in some fashion.
I’m going to give the minority report on this one. The reason languages that could have comparable REPLs, like node, end up not having one is because there are better options.
The problem with REPL based development is that it’s easy to get in an inconsistent state with a mix of old and new code. The result is when you run the code from scratch, it doesn’t work even though it did in the REPL.
With file watchers, like nodemon, and proper testing, you get a comparable feedback loop while always running the actual code as it is in the source files.
So, to sum up, the main reason that other languages don’t prioritize a REPL is because it’s not necessary. It’s one way of doing things, but not the only way.
seancorfield, you’re right–loading packages is different in OCaml’s utop, so that is a semantic difference, and there were probably times when I experienced something different as a result. I may use REPLs differently from some folks, and it might not matter as much for me, but it’s still a good point. And no, the repl isn’t included in most applications! OCaml people would hate that idea. (The fact that the REPL uses bytecode isn’t the most relevant point, I think. You can compile OCaml to a standalone bytecode application, and for most purposes it should have the same semantics as compiling to machine code. But of course there can be differences.) I will say that the fact that OCaml or Haskell have serious REPLs–whatever the differences with the Clojure situation–is one of the reasons I was willing to take those languages seriously. Same thing for R. Compile-only languages are not as convenient.
The interesting thing about @didibus’s and @Richard_Heller’s points is that they seem to focus on the same dimension of the Clojure REPL. The fact that you can build a world up in it, which is great, is the same reason that that world can get out of sync with what you expect.
You’re absolutely right, this does happen and I often find myself, at least when running tests, doing:
ns-unmap *ns* 'some-test-name
when I have renamed a test to something else and the old one is still there in memory, which can be a real PITA. In this respect, REPL-driven development a la Clojure/Script at least is not perfect, and if you don’t know what you’re doing you can certainly run into situations where you are scratching your head wondering why things aren’t working as you believe they should be. That said, with a bit of practice it doesn’t take too long to figure out all the little quirks that you could run into, and as long as you’re ok with them, it’s an amazing, not to mention fast, way of developing compared to the more traditional way(s).
This is actually a false dichotomy: with Clojure/Script, you aren’t limited to REPL-driven development, you can choose to ignore it completely and develop in the traditional way. Or you can do what I assume most people do, and that is to develop within the REPL, save regularly, and run your tests as you would normally, and get the best of all worlds.
Sort of. The question is why other languages don’t prioritize a REPL. It’s because the downside far outweighs the upside. The language features that allow the REPL are generally not desirable.
No Lisp has ever had the widespread adoption of JS or python, so don’t have the same restrictions. JS could have easily prioritized malleability when designing the module systems, but didn’t. It’s generally preferred that modules are isolated and can’t be changed at runtime. That prevents a Lispy REPL, but is better overall. Same thing with const. It breaks the REPL but is generally better for code. Standard, predictable behavior is prioritized over the “anything can be changed at any time” that a REPL requires. JS has actually been moving away from REPL friendly features because of that.
There’s a similar argument around macros. Languages don’t prioritize macros because, in general, macros are absolutely evil. Large codebases that are macro heavy become much harder to maintain when there’s a lot of developers touching the same code.
As far as the speed argument, if you look at start to finish how long it takes to implement a feature, I actually find using the REPL to be much slower. I only ever connect to one when using Lisps because the editors don’t work without it.
Sometimes I wonder why you even bother with Clojure when you espouse views like that?
I consider Clojure’s REPL to be its superpower. It’s why I prefer to use Clojure instead of other languages. The REPL’s pros far outweigh the cons for me – and many other Clojurians – and it’s why we find other languages that lack such a powerful tool to be sub-par and why we don’t enjoy using them.
No, the question was, and here I quote:
“Why do other languages dont have a REPl like LISP’s?” [sic]
Or if you prefer:
The most correct answer was already given by Sean Corfield in his first response above, which I can summarize by stating simply: they can’t. At least not without a great deal of difficulty, and that isn’t something worth pursuing in most cases.
It has absolutely nothing to do with any deliberate design choice on the part of the respective language designers to not introduce certain features that would make this possible. In fact, it isn’t even about the design of the language, but the design of a specific tool, in this case the REPL.
You admit that you “sort of” agree that your argument is a false dichotomy, yet you persist in pursuing the same train of thought nonetheless. There is no either/or choice to be made here. Let me illustrate by briefly describing my current setup. I have a project with server-side Clojure code in .clj files, client-side ClojureScript code in .cljs files, and a large amount of code in .cljc files. I develop in emacs with one source window, and two REPLs, one a Clojure REPL, the other a ClojureScript REPL.
As I make changes, the first thing I do is to compile the top-level form surrounding the change to make sure it will eventually compile in the source file. I then test it by running unit tests which I launch from, wait for it, yes, the REPL. This takes a few seconds tops. If anything fails, I can go back and fix it immediately, and go through the process again, until it passes. Once all the tests I think need to be run work, I then hit the save button on the source file, and that triggers all other tests and so on.
The key thing is that I get instant feedback on whether something works or not in the context of the running program, and then I continue the development process as I would if I didn’t have a REPL. Sure I could do all of this without a REPL, but I would have to be nuts to do so as I would be spending a considerable amount of time in the loop: make a change, save the source file, compile, run all tests, run the resultant compiled program, get the compiled program into a state where I can check my change works, test the program manually to see if the change behaves as it expected, then go through the same process again for every single change?! Are you seriously trying to say that this latter process I’ve just described is faster than the process where you don’t have to do all of those things:
Because that’s what it sounds like.
In short, you are driving at something which simply isn’t true: it’s as if you are saying that in Clojure/Script you can only develop in the REPL or you can develop by compiling source files and going through the same process as all the other languages do. But that’s not how it works. As I’ve already stated, you do both. The REPL is a tool that is added to the mix to support the overall development process, to make it considerably faster and to give you direct access to the runtime of your program. If you really do find the REPL to be much slower, fair enough, but the majority of people don’t.
According to whom exactly? You, certainly. But are you including everybody else out there who programs in JS, Python, Ruby, Erlang, Haskell or any other language which provides a REPL? I’m reasonably certain that the majority of people, if they had access to such a feature, would absolutely love it. But the reason they don’t have access to it is because it’s too difficult to implement in all those other languages. That’s all there is to it.
There’s a few ways you can evaluate languages and tooling:
- Quantitatively, which is hard, but most data actually show little significance to language choices. But of the small effects it shows, it does seem like Clojure always fall in the most productive, most safe language bracket, and in the middle of the pack for performance.
- Qualitatively, which will be things like does it have a REPL, can it run on platform X, is there library to help do Y, does it have test tooling, what is the memory profile when doing Z, how easy can I hire an expert in it, etc. You need to know what qualities you’re looking for or value to leverage that method.
- Preferentially, as in, what do you personally know best, are most familiar with, are most effective in, find more engaging to use, have more fun using it, find easier to leverage, etc.
I feel you’re trying to pretend like your “preferential” justifications are somehow quantitative or qualitative when they’re not.
Now, I believe the preferential dimension is very important, so I’m not dismissive of those reasons, but I like them explicitly called out. Because when it comes to influencing other’s who might not yet have a preference and are looking to explore and eventually develop some, I find it manipulative to claim ones preference as quantitative or qualitative facts.
I’d personally challenge anyone who’d claim that a fully dynamic language with proper support for a REPL and the required available tooling, and the use of REPL driven development yields superior or faster outcomes to all other languages and tooling without. There’s just no strong quantitative data about that. You can make qualitative arguments for why you’d want that, and you can prefer working in that way, and that’s all there is to it. Similarly, I’m challenging people who claim the opposite, that there are legitimate quantitative demonstrations that this way of coding is detrimental, slow, or yields worse outcomes.
That doesn’t mean that YOU personally will not see improvements to your productivity and the quality of the programs you output though. This is why I don’t dismiss the preferencial dimension. The developer is still the most impactful when it comes to quick delivery of software, of high impact, and of high quality.
That’s why using a language and a set of tools and methods that you’re most efficient with, most effective, most engaged, having the most fun in, that can sustain your concentration, help you where you personally have weaknesses, keep you motivated and focused, will have a dramatic effect.
Which is why I believe you, REPLs don’t work for you, and that’s totally fine and understandable, but understand that for others it is empowering and a huge boon to their productivity, happiness, motivation, engagement, and the resulting quality of the programs they develop with it.
Just an aside, and it’s probably that I only visit forums for a few languages, but it’s in discussions of Clojure where I find that people say things like “this language makes me happy”, as @didibus more or less did. (In another language forum I frequent, I have seen people say “this language is awesome” or “I love this language”, but never “it makes me happy”.) There must be people who say this kind of thing for other programming language contexts, but still …
The REPL isn’t the only reason that Clojure makes me happy, but it’s a contributing factor. (Common Lisp doesn’t make me happy, though its REPLs are as good or better.)
So you can actually do most of this with GDB. It sounds like Erlang. Similarly, the main slightly unergonomic thing would be that you need to recompile and reload your compilation objects. However declaring new vars and functions on the REPL or in a separate file is a minor distinction.
I would say no one quite does GDB-driven development though. It’s just one stage in the development process. As Richard says, there are better options
The one thing I really miss from GDB are the error stacks. Not only are the errors easier to read, but crucially you get program state at the crash site. It’s like you put a #break at the exact right spot. So you can inspect the variables and see what went wrong. Usually you immediately see the issue
By contrast, I waste a crazy amount of time in Clojure just hunting down what went wrong… inevitably using the damn REPL :). As has been pointed out it accumulates stale state and is sorta messy. But that’s because I’m forced to poke around to figure out my errors. If I’m doing too much in the REPL that’s usually a bad sign. It’s better when you have clean files you can re-run. If I got a crash state and clear errors I’d prolly used the REPL a lot less. It’s still very nice for inspecting and poking around data, but I don’t really see it as essential.
I personally use Clojure not for the REPL but for the awesome datastructures and functional programming. It’s like the C++ STL on steroids but with way less cognitive load. At the same time I have access to the whole universe of Java libs
I think this highlights a difference with what is usually considered REPL-driven development. I usually never type anything into “the REPL”, rather I edit my source files and send the changes to the REPL. I never change a top-level binding any other way, as that will surely lead to the inconsistent state you describe. If I do want to poke around, I do that inside comment blocks in my source files. That yields two benefits: I can extract my poking as tests if that makes sense; and I can I can keep it around for setting up development state while I’m still figuring things out.
I don’t find the REPL as a place of writing code all that useful. However, as a place to send my file changes while writing my program, and as a place for evaluating arbitrary expressions and seeing the results, I find it indispensable. I have only had that same level of interaction with a Smalltalk image.
It is true that the node shell allows me to evaluate arbitrary expressions, but it does not allow me to run my program and rewrite it while running. The tool provided by node does not allow me to grow my code interactively.
The data structures and language design choices are what drew me in. The interactive developer experience is what keeps me coming back.
One of the reasons I really like Clover (for VS Code, and previously Chlorine for Atom) is that the REPL is readonly – you can only eval code from files into it, you cannot type into it so it really encourages a “better” workflow.
Exactly, there are multiple good environments that encourage not typing into the REPL. I’m using Conjure in Vim, but thats the same story. I would advise anyone to at least get comfortable with one of those environments, if only to get better at the interactive development story in Clojure.