top | item 23791152

Programming at the REPL (2018)

141 points| tosh | 5 years ago |clojure.org | reply

121 comments

order
[+] verytrivial|5 years ago|reply
I had my mind lightly blown watching someone using Squeak Smalltalk to interactively prototype something or other.

They purposefully called a method which did NOT exist, used the debugger to partially unwind the #doesNotUnderstand stack, implemented the method, which then returned the expected result as if the method had existed all along. Benefits of super late binding I guess.

[+] ssivark|5 years ago|reply
This is like the best case example of test driven development.

I don’t understand what is stopping other languages from supporting “live debugging” of this kind.

[+] veddox|5 years ago|reply
Sounds cool, but what exactly is the advantage of doing it like that?
[+] piercebot|5 years ago|reply
The REPL is still something that I haven't quite mastered, but what I enjoy doing right now is working in files and using a REPL connection to evaluate expressions within the file. It feels like a happy medium between "do everything at an (ephemeral) REPL prompt" and "do everything in a file, save, and reevaluate the file."

My weapon of choice right now is vim-iced[0], which is a batteries-included VIM plugin that, in addition to providing a REPL and keybindings to evaluate expressions under the cursor, also allows me to auto-format my files on save.

[0]: https://github.com/liquidz/vim-iced

[+] mbil|5 years ago|reply
I don't have much to add to this discussion except to say that after years of repl-driven development in Clojure I've switched to Ruby as my primary language, and the iteration cycle is just really slow. I feel like I've traded in my fighter jet for a commuter car.
[+] modernerd|5 years ago|reply
Clojure[Script] is worth learning just to experience a REPL-driven development environment.

Some editors and IDEs support it better than others, though, notably Emacs and Atom. Cursive (for IntelliJ) is pretty good, and Calva (for VS Code) is promising, but they lag behind in polish and features when I tried them. For example, I don’t think Cursive has inline output next to the expression you’re evaluating like Proto-repl for Atom does, and once you’ve tried that you don’t want to live without it.

I don’t write much Clojure these days but miss the REPL-driven experience. I recently discovered Quokka and Wallaby for JavaScript/TypeScript:

https://quokkajs.com/

https://wallabyjs.com/

They offer a great experience that beats console.logging your way to success or step-through debugging by far.

Just highlight the expression you’re interested in and get inline output (in the paid version of Quokka) right in your editor, including evolution of values in for loops and the values of data retrieved from fetch requests. It has greatly shortened the feedback loop for me and both plugins were much easier to set up and configure than Clojure’s REPL integrations.

[+] lukashrb|5 years ago|reply
I always wonder why repl driven development is not as popular as it (in my opinion) deserves to be? Am I missing something here?
[+] reddit_clone|5 years ago|reply
Most people never get to experience developing in lisp family languages and just miss out on the whole REPL experience.

They have to write reams of unit testing code , mocking and faking the world just to see some code executed.

[+] jonahbenton|5 years ago|reply
There is a non-virtuous (vicious not the right term) cycle at play.

Many problems are only solvable with compiled languages, for performance, correctness, or other non-functional considerations. Many people are first taught on compiled languages, and bring those approaches to dynamic languages. The popular dynamic languages, in their standard/common toolchains, do not have robust repl support (looking at you Python and Javascript). The absence of a robust repl workflow means people who are exposed to it in those languages aren't really impressed by it. Really too bad.

[+] kgwxd|5 years ago|reply
I think it clicks quickly for some people but not others (me). I don't get to use Clojure for work currently (.NET shop) but I really like it. In my spare time I remake a lot of things I do at work to learn Clojure using real world scenarios.

I've read this guide several times over the past few years, watched a bunch of the videos and I still find myself stoping/starting the repl very often because it feels difficult to keep the current state of the environment in my head, or something isn't updated as I expected, there's an error I can't decipher in the REPL that the compiler gives better info on. I feel I need to restart constantly, it's still my main workflow. I'm not sure what it is that's not clicking for me, but I keep trying. Either way, I'm no worse off than with my C# workflow.

[+] nickjj|5 years ago|reply
I think it's maybe related to 2 things, especially for web development.

1. Unless you strictly design functions that are decoupled from any type of UI (such as your back-end "business logic"), it's not straight forward to do REPL driven development on its own.

2. If you're going to go down the path of using the REPL, you might be inclined to do TDD instead because now your efforts are saved to repeatable tests rather than a short lived REPL.

[+] Gunax|5 years ago|reply
Is there a reason it's always functional languages?

Despite being HNs favourite paradigm, functional languages really are an obscure to most people.

As soon as there is a (convenient, useful) REPL for Java or c#, it will get picked up.

I know Java added a repl a few versions ago, so maybe we will start to see this in the Java world.

[+] kamaal|5 years ago|reply
>>I always wonder why repl driven development is not as popular as it (in my opinion) deserves to be?

It's not mainstream, so one knows how to. Basically somebody needs to show people how repl driven development works, and will be useful.

May be YouTube tutorials or gifs as to how to do this.

[+] Scarbutt|5 years ago|reply
Because it's no different than running tests in a language with a fast code/run cycle. Clojure by necessity, needs to have a REPL, without one it would be unusable. The reason: Clojure startup time is pretty bad.
[+] cameronperot|5 years ago|reply
I do a lot of scientific computing, mostly in Julia, and having a REPL at my disposal is extremely useful. There's a great VIM plugin [1] that enables you to send code from the editor to a terminal to be executed, e.g. a tmux session which has a REPL open. It can be coupled with other nice plugins such as vim-ipython-cell [2] and vim-julia-cell [3].

[1] https://github.com/jpalardy/vim-slime

[2] https://github.com/hanschen/vim-ipython-cell

[3] https://github.com/mroavi/vim-julia-cell

[+] jimbokun|5 years ago|reply
A REPL can also be excellent for operations work and ad-hoc tasks. I use it a lot when I need to extract some data from a REST API. Can create a toolbox of methods over time for making calls, and add new ones in an ad-hoc fashion, then use those to script larger tasks. Exploration, analysis, minor ETL tasks, debugging state of deployed systems, etc. etc. can all be enchanced by a REPL.
[+] entha_saava|5 years ago|reply
Can someone knowledgeable explain how are lisp REPLs different from Python / Ruby REPLs? What is the differentiating point of REPL driven development?

Is it that you interactively test your functions and add them to source file once they work? Or is it that with editor integration, you can load functions to REPL as they are defined? Am I missing something obvious?

[+] mikelevins|5 years ago|reply
I've answered similar questions several times over the past few years, but I don't mind repeating myself. It offers me a glimmer of hope that my preferred way of working may not fade away, after all.

Consider the standard Common Lisp generic function UPDATE-INSTANCE-FOR-REDEFINED-CLASS (http://clhs.lisp.se/Body/f_upda_1.htm). It reinitializes an object when Lisp detects that the object's class definition has changed.

Ask yourself this: who would call such a function? Why would anyone ever invent it? Not only did someone invent it, a committee of some of the world's smartest and most experienced Lisp programmers wrote it into the ANSI standard for the language. What were they up to?

UPDATE-INSTANCE-FOR-REDEFINED-CLASS is not a weird anomaly; it's part of a carefully-considered set of features and protocols designed to support a specific style of programming. The Lisp runtime calls it for you automatically when it touches an object whose class definition has changed.

If you've defined a method specialized for it, then Lisp executes that method to rebuild the touched instance as if it had originally been instantiated from the class's new definition, and then your program goes its merry way. If you didn't specialize UPDATE-INSTANCE-FOR-REDEFINED-CLASS for this case, then the Lisp drops you into a breakloop.

A breakloop is an interactive repl with full access to all of the runtime's memory and all of the language's features, including visibility into the whole call stack that landed you in the breakloop. You can wander up and down the call stack, inspect anything in the runtime, edit bindings, redefine types and functions, and resume execution either at the point of control where the breakloop started, or at any other point for which the breakloop exposes a restart.

UPDATE-INSTANCE-FOR-REDEFINED-CLASS is not the weird fever dream of a confused eccentric. It's part of a purposeful system design intended to support a style of programming in which you build a program by interacting with a live runtime and teach it, interaction-by-interaction, how to be the program you want, while it runs.

It's a particular example of a general approach to programming best exemplified by these old systems. That general approach is the answer to your question: "Can someone knowledgeable explain how are lisp REPLs different from Python / Ruby REPLs? What is the differentiating point of REPL driven development?"

The differentiating point is that the entire language and system is thoughtfully designed from the ground up with the assumption that you're going to be changing your work in progress while it runs, and that you should be able to change absolutely anything about it as it runs and have a reasonable expectation that it will continue to work while you do it.

I like to call this style of programming "programming as teaching", and distinguish it from the much more widespread "programming as carpentry", in which the programmer is, metaphorically speaking, at a workbench banging together artifacts and assembling them to see how they turn out.

To be clear, I do not claim that the teaching approach is objectively better than the carpentry approach. I claim only that I, personally, am happier and measurably more productive using the teaching approach. I know that some other programmers report the same thing, and I suspect that if the teaching style of programming were more widely known, then there would be more programmers who prefer it.

There are several sibling comments that assert that any language can be made to support repl-driven programming, or that offer various languages and systems as examples of repl-driven programming. I'm sure that's all true, for some relatively restricted version of repl-driven programming, but the gold standard in repl-driven programming is programming as teaching in the style of old-fashioned Lisp and Smalltalk systems. These old systems offer amenities that the younger alternatives touted here do not match. I want more people to be aware of what they're missing.

Starting in the 1980s, I grew accustomed to systems that could start from cold in about a second, presenting to me a complete interactive development environment with all tools preloaded and ready to work, with the whole dynamic environment of my work in progress in the same state it was in the last time I was working with it. Moreover, I was accustomed to being able to take a single file from one machine to another to reproduce that same whole working environment equally quickly and easily on the new machine.

I could save the entire dynamic state of the running system to an image file, a serialized version of the running system's memory. I could later start up the system with that image file and be exactly where I was when I saved the image, right down to the positions and contents of all the open windows. I could save an image showing some bug or some strange behavior and give it to a colleague so that they could see it, too, and interact with the restored dynamic state to debug it.

I enjoyed comprehensive whole-system reflection that enabled me to view and edit absolutely everything in the running system while it ran. I could inspect absolutely everything, including the development environment and all its tools, interactively change any variable or field value, redefine any type or function, and continue to work with the changed system without stopping and restarting. (Obviously, if I made a bad change I might break the system, but remember, I could kill it and get back to where I started in a second or so).

I could start some process running--perhaps a 3D animation in a game, or a discrete-event simulation, or whatever--and change any values or definitions I liked to see what changed in the running process, without stopping the process to rebuild. For example, I could tell a rotating copper cube to become a glass icosahedron and reasonably expect to see my changes immediately reflected in the running program. This property is invaluable not only in games, simulations, and any kind of work with a visual-design component, but also in any kind of exploratory programming, where you're constructing data structures and evaluating expressions interactively to test your ideas.

Similarly, I could build some speculative data structure to explore an idea, and define some functions to operate on it. I could evaluate those expressions to see their results or to change the example structure. I could inspect the structure interactively and edit it in place if I think something different would work better. If I think a problem is caused by some structure or value in it, I could use the inspector to change it and see. If I thought one my my functions was doing something I didn't expect, I could insert a call to break, to activate a repl from inside the function call that would enable me to inspect and edit the data structure, redefine the function, and continue from there.

Anything the development system could do, I could do by typing an expression into the repl. As an example, nowadays you can still rebuild the whole Clozure Common Lisp environment from the ground up by typing (rebuild-ccl :full t).

The point is not that I would want to rebuld my Lisp from the repl all the time. The point is that the repl doesn't impose any aribtrary boundaries on what I can do. If the language and development environment can do it, I can do it from the repl. This is one of the properties that distinguishes the whole-system interactive design of these old tools from the more limited repls offered by newer ones. In pretty much every repl I've used other than old-style Lisps and Smalltalks I'm all the time stumbling over things you can't do from the repl.

I mentioned breakloops above. Their absence in younger languages and tools seem to me like some sort of sin, like we're tragically abandoning some of the best lessons of the past. Few newer development systems have them, but they're incredibly useful--at least if the language runtime is designed to properly support interactive programming.

A breakloop is a repl with all of the same affordances of the normal repl, but extended with all of the dynamic state of the control path that invoked the breakloop. If an error or an intentional call to break triggers a breakloop somewhere deep in a stack of recursive function calls, you get a repl that can see every frame of that stack, and every variable and value lexically accessible from it. You can browse all of that whole, change values, and redefine functions and types. You can resume execution at your leisure, and any changes you made in the breakloop will be visible in the resumed computation just as if that's how things were originally.

Proper breakloops don't just improve error messages; they replace them wholesale with an entire species of programming that lays the whole dynamic state of the system out on the table for you to examine and modify while the program continues to run.

Moreover, everything I just described about breakloops can also be automated. These old systems provide not only interactive tools for rummaging through the dynamic state of a suspended computation, but also APIs for handling them under program control. For example, you can wrap an arbitrary function call in condition handlers that will either drop you into a breakloop and enable you to vivisect the program state, or consult the dynamic state and compute which of several restarts to activate in order to transfer control to a path of your choosing.

I'm banging up against HN's length limit, but the above, I hope, goes some way toward answering to your question.

[+] divs1210|5 years ago|reply
The REPL has spoilt me. Working with languages without a powerful REPL, even ones with a shell like JS/Python/Lua/Ruby feels like a regression comparable to moving from zsh to windows cmd.
[+] nonbirithm|5 years ago|reply
I have managed to program a REPL in Lua for game development. I also added a feature that lets you reload any single Lua module at runtime, thanks to late binding through tables. Nobody seems to standardize on a good way of doing it so I had to roll my own but I've saved countless hours of restart time doing so and had fun programming at times.

There really, really should be a standard protocol for communicating with a REPL in a running program like there is for IDEs in the Language Server Protocol. Maybe that way the concept would become more mainstream if you could just take any REPL client and walk up to a program with a REPL server and interactively explore its state. The closest thing to said protocol is nREPL which is what Clojure uses, but there are a lot of nonstandard extensions to the protocol for edge case features only applicable to Clojure, so almost every client assumes you're using Clojure and it greatly reduces the viability of using it for Lua or other languages. It doesn't have the backing of something like Microsoft either, I guess because this REPL thing lives in its own world with the Clojure userbase.

I opened an issue on Clojure's REPL integration with Emacs for supporting more languages[1]. I wanted to take advantage of the effort spent on Clojure's client side tooling for things like the command prompt and state visualiser but apply them to at least Lua and allow others to add their own support for other languages. It would be a really big effort but I've already seen the value of it by integrating a REPL with projects written in Lua. As you said I can't ever imagine using a dynamic language without one again.

[1] https://github.com/clojure-emacs/cider/issues/2848

[+] errantspark|5 years ago|reply
Whoa? JS? Really? The Chrome Dev Tools are probably the best REPL programming environment I've ever used.
[+] Angeo34|5 years ago|reply
If you think zsh to cmd is a regression you should try out bash. It's vastly superior to zsh in literally every regard. If you still aren't stratified use fish.

Genuinely never got how people use zsh. It's the slowest and clunkiest shell I've ever witnessed it's fucking slower than most electron gui software.

[+] veddox|5 years ago|reply
Out of curiosity: what‘s the difference between a REPL and a shell?
[+] derision|5 years ago|reply
I started learning Clojure yesterday,I've gone through the docs here and have a grasp of the language. Does anyone have some good resources for further learning? Maybe some high quality tutorials? I'm mostly interesting in web / apis
[+] nanomonkey|5 years ago|reply
Eric Norman at PurelyFunctional.tv has the best video courses on Clojure that I've seen. I found it super helpful to pick up techniques on how to get things done. Some of clojure's concepts feel debilitating at first (immutable data structures and pure functions), so watching a seasoned programmer/trainer at the REPL building is necessary. He also goes through setting up Emacs and other environments, along with using Reagent (React for Clojurescript), etc.
[+] goostavos|5 years ago|reply
I don't think there's a single best Clojure book, so I'll recommend several (all available on Safari if you have that). For general language, syntax, idioms:

* The Joy of Clojure (whirlwind tour of the language and Clojure philosophy.) * Brave Clojure (A very wordy, hand-holdy approach. I really like this one for dense topics like core.async which can take time to wrap your head around) * Programming Clojure - just an all around solid reference. Once you've got a command of the language, this becomes more valuable imo .

Now, there's a large gap between knowing Clojure's syntax and writing effective programs in the language. Clojure is not Python, or Haskell, or Java, and, as such, you don't build programs in the same way. So, as a final recommendation, I'd suggest the book Clojure Applied. This book deals with going from "I know the language" to "I know how to build things in the language"

For the web, get ready to beat your head against a wall for hours and hours on end ^_^. Lisp's "small pieces" philosophy is both a blessing and a curse. Outside of a framework called Luminus, and a few leiningen templates, the way to do things in Clojure land is by stitching a bunch of ad-hoc libraries together by hand, which means a lot of evaluation of not-too-popular github repos.

Once you've got everything dialed in though, it's an absolute joy to use.

[+] hotcrossbunny|5 years ago|reply
If not already mentioned, John Stevenson's Practicalli site and YouTube channel are very good and active. If you are going down the Emacs route for the first time, his contributions in and around and explaining Spacemacs are pure gold
[+] hotcrossbunny|5 years ago|reply
Also check out @reborg and his much underrated YouTube channel. I really hope he can get his book finished
[+] slifin|5 years ago|reply
Living Clojure is my favourite learning book on Clojure

Also I recommend making friends with someone who can show you their structural editing and REPL setup, preferably more than one friend because it's like trying on clothes

[+] siraben|5 years ago|reply
Programming at the Haskell REPL is a great experience, whether it's if you're exploring data, testing out a parser, or running an effectful computation with a given state. On the other hand when a language is impure like Scheme or Clojure I've often found myself needing to reload the REPL again and again to get to a clean state.
[+] thom|5 years ago|reply
I think obviously it's much _easier_ to create a mess in Clojure this way, but I think in most sane codebases the pure and impure stuff is quite neatly demarcated. Generally people have learned to structure systems as some sort of root data structure containing various components, which can be started, stopped or reset at will. You have to put that in place yourself, but I've not worked on a project where it's been an issue in many years.

For what it's worth, I have found dynamically loading new libraries at runtime easier in Clojure than Haskell, which is another source of REPL friction for me, although I rarely use the latter these days.

[+] tgbugs|5 years ago|reply
Here is a question that I have about Clojure and its REPL. I have tried 3 times over a period of a year or so to get it to behave like a common lisp REPL and every single time I have hit a brick wall called project.clj and/or deps.edn (which open a terrifying black hole to maven). My simplest use case is to be able to call `(expt 2 10)` from a bare clojure install without leaving the repl. As far as I can tell it is impossible to achieve this. I finally gave up and wrote a gentoo ebuild to install the numeric tower just to see if it was possible [0]. What I learned was that I have no idea how clojure libraries interact with the JVM. `pomegranate` gets the closest but I'm still fairly certain that you can't install that from a repl without resorting to hackery [1]. I think I understand the tradeoff toward using project.clj and deps.edn to create sane and static environments for a project, but the friction is larger than nearly any other language that I have tried because you are practically forced to create a project structure in order to do anything.

0. https://github.com/tgbugs/tgbugs-overlay/blob/master/dev-jav... 1. https://github.com/clj-commons/pomegranate

[+] siliconc0w|5 years ago|reply
I'm a big fan of REPL programming and miss it a lot when I work with compiled languages. There is definitely a 'right' and 'wrong' way to do it though. REPL programming doesn't replace the need to write tests and it actually allow you to write those tests faster as well as build more realistic faked versions of upstream or downstream dependencies.
[+] bonestormii_|5 years ago|reply
Programming in the REPL is something I do all the time and generally enjoy, but I sometimes feel like the actual design of the program then becomes too focused around REPL usage. Too much abstraction can harm performance and waste time. The REPL encourages this by making you think about the internals of your program as an interface that will actually get used, when in reality some function may get called twice ever from within your own code, and isn't really meant to be exposed. But because you are repeatedly invoking it manually via the REPL, it feels like an interface worthy of considerable design.

Maybe it is, and maybe it isn't. I've just noticed this tendency in myself when working in REPLs. Not every part of your program is an API.

[+] EvanAnderson|5 years ago|reply
I've enjoyed noodling around w/ Javascript in various simple REPLs. I love the feedback, but it also feels a lot like using "ed" versus a visual editor. I find myself writing code in a text editor, then pasting bits and pieces into the REPL.

I'd love to have tooling that let me develop in a REPL, but also had a text editor to let me edit objects / functions / etc in that manner, too.

[+] adamkl|5 years ago|reply
Well, with proper REPL integration, you never really type/copy/paste into the REPL.

You write your code in a source file like normal, highlight what you want evaluated, and have the REPL evaluate it.

Clojure has a great REPL because you are effectively working inside your own codebase, as it runs. Its a strange idea to wrap your head around, but very cool.

You can get something similar to this approach in JavaScript using Quokka[0]. It's not as complete as a Clojure REPL, but its still quite nice.

[0] https://quokkajs.com/

[+] oweqruiowe|5 years ago|reply
If anyone hasn't tried Clojurescript's browser REPL, it's super easy to use now when using Shadow-CLJS for your project.

When you boot shadow up it'll create an nrepl server for you, you can just connect to it and invoke `(shadow/nrepl-select :app)` and it'll latch on to the running application `localhost:3000`. And it works really well, very awesome.

[+] keithasaurus|5 years ago|reply
The clojure REPL is nice, don't get me wrong. But I think the reason people rely on it more than in other languages is because of clojure's slow startup time. If you could just make a change and run tests immediately, you'd see much less REPL abuse.
[+] fmakunbound|5 years ago|reply
Common Lisp REPLs and Smalltalk have ruined me for life as far as that goes.
[+] tartoran|5 years ago|reply
Can you expand on that?
[+] riazrizvi|5 years ago|reply
Am I missing something? This article seems very thin.
[+] x87678r|5 years ago|reply
Bob Martin was talking about this in May too. I think he decided to go back to tdd. http://blog.cleancoder.com/uncle-bob/2020/05/27/ReplDrivenDe...
[+] phoe-krk|5 years ago|reply
I'll copypaste my comment that I've left at the HN discussion of that blogpost, https://news.ycombinator.com/item?id=23333853:

This is a PEBKAC, not a problem with REPL-driven development. He had all the test bodies written as input in the REPL, he had all the test results printed as output in the REPL. Why didn't he turn these into unit tests as he programmed?

That's a simple lack of foresight on the programmer's side that is then blamed on the tools that were used.

[+] slifin|5 years ago|reply
I knew his article would get misconstrued this way to clarify he almost certainly will be using a REPL in Clojure (even running tests happens through a REPL in Clojure) but I think what he's trying to convey there is that he's not designing in the REPL but rather with TTD

Which honestly is fine but most people reading the article won't be in a REPL based language like smalltalk or lisp so will just understand REPL bad TTD good

[+] lukashrb|5 years ago|reply
Not that I'm an expert but I guess he was doing it the wrong way. I just use (comment...) blocks to preserve what I entered into the repl. These blocks are also easily turned into test cases. It feels kinda similar to tdd.