Why you should be using a REPL all the way to production
Published: 2023-09-04 09:34:22

Let’s talk about one of the most productive tools a coder can use: the [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)! The first half of this article gives you a short introduction to the subject if you’re not a coder. If you are a coder, skip ahead to the second half where I talk about using REPLs in production (“with great power comes great responsibility”).

I recently released a long overdue update to the [Seneca](https://senecajs.org/) REPL plugin (A [Node.js](https://nodejs.org/) [microservices](https://martinfowler.com/microservices/) framework). Since I’m also a fan of [fastify](https://fastify.dev/) (a web framework for Node.js) I couldn’t resist submitting a plugin proposal for a fastify REPL too, since we’ve been having far too much fun with our Seneca REPL, and fastify users are missing out!

Why this renewed focus on a feature I first built years ago? What’s so great about REPLs anyway?

A REPL (Read-Execute-Print-Loop) is a coding tool that lets you interact directly with a running system. The browser console is easily the most used REPL, and essential to the development of any complex web application. You can type in code directly, and see results right away. Rather than waiting for your code to compile, deploy or load, you can make changes immediately. You get direct feedback with no delays that lets you stay focused on the work.

Programming systems provide a REPL as an interactive text interface that lets you write code, usually in a more lenient or shorthand form. You don’t have to formally declare variables. You get access to predefined useful state objects (in the browser, you can access the `document` object, among many others). The essential thing is that you are given access to the live running system, whether that is a web browser, or a local development server, or something else.

Coders use a REPL to debug problems directly, to inspect data to figure out what is going on, to experiment with new code to see what works, to get a feel for a new API (Application Programming Interface), to manipulate data, and even to administer deployed systems.

If a new system doesn’t yet have a REPL, coders will always build one for it. Why does this happen? In more ordinary software development, you write some code, save it, and then run the code, often as part of a test suite. If you’re lucky, your test suite is pretty fast, on the order of seconds. More usually, it can take minutes to run for a production system.

There’s a huge amount of friction in writing or changing some code, then switching to another place to run the code, and then waiting for results. Programming is a lot like the spinning plates performance act. The coder has to spin up multiple mental states and keep them all in their head, so that they can tie the pieces together to solve the problem. Get interrupted while you’re waiting for your tests to complete? Good luck fixing that bug–now you have to rebuild your mental state again, which can easily take a few minutes of focused thought.

As an aside, this is why “collaborative” open plan offices make your developers super unhappy and unproductive–they need to [concentrate, not chit chat!](https://www.linkedin.com/posts/ian-mcclean-46b7681_minimise-distraction-activity-7056953069778939904--b99)

The coding tool that makes unnecessary wait time go away is the REPL. You don’t have to context-switch half as much. The code is run and you see the results right away, so you can stay focused and get the solution. Often you’d be able to copy and paste the rough code out of the REPL into your editor, and fix it up to be production grade.

Not all code is the same. REPLs work best with “business logic” code, and user interface code. When you’re writing code that’s more about computer science algorithms, or core library code, you spend much more time just thinking rather than smashing the keyboard. But when you have a documented business requirement, the work is more about gluing the parts together than inventing new algorithms. What counts is being able to ensure your data has the right structure, and you are calling the right APIs in the right way. A REPL makes this work much easier.

Where (and when) does the idea of a REPL come from? Probably in 1958, from [John McCarthy](https://en.wikipedia.org/wiki/John_McCarthy_(computer_scientist)), the inventor of the Lisp language. Interactive evaluation of expressions was an essential feature of [Lisp](https://twobithistory.org/2018/10/14/lisp.html) right from the start. If you had had to code using [punch cards](https://www.computermuseumofamerica.org/2022/09/16/what-are-punch-cards-in-early-computers/) up to that point, you’d probably be pretty keen on having a REPL too. 

The [Smalltalk](https://squeak.org/) programming environment took the idea of a REPL to the next level, by making the entire system “live”. You coded by manipulating the state of the live system using an editor that was basically a REPL. Saving your project was the same as serializing the current state.

Although it didn’t really work out, the [database query language SQL](https://renenyffenegger.ch/notes/development/databases/SQL/history/SEQUEL) was supposed to be a way to “talk” to databases using human-like language. But us programmers still get the benefit of an interactive language to manipulate data.

The REPL environment that most non-coders would be familiar with is a spreadsheet. You edit formulas and you see immediate results. This is very gratifying! Programmers enjoy the same serotonin kick. And it gets the work done faster. [Enjoy the cheese!](https://www.youtube.com/watch?v=kOO31qFmi9A)

With machine learning finally starting to deliver, many folks are getting exposed to another form of the REPL: the interactive notebook. First seen in the realm of statistical analytics and mathematics, with systems like [Mathematica](https://www.wolfram.com/mathematica/), notebooks such as [Jupyter](https://jupyter.org/) now provide a comfortable interface to complex underlying systems.

And we are not far off the holy grail of REPLs–the fully competent natural language interface provided by the new generation of artificial intelligence LLMs ([Large Language Models](https://mark-riedl.medium.com/a-very-gentle-introduction-to-large-language-models-without-the-hype-5f67941fa59e)). Just playing around with a service like [ChatGPT](https://chat.openai.com/) will give you an instant feel for why coders are so keen on having a REPL available.

There’s even a [marked-for-unicorn](https://www.badunicorn.vc/) status tech startup called [repl.it](https://replit.com/) that provides software developers with, you guessed it, an online REPL-like environment for coding.

OK, let’s get a bit more technical. If you’re a coder and have used REPLs occasionally but have not found them to be that useful, let’s dive into how you can use them to get some [super powers](https://www.youtube.com/watch?v=zgt-jNqbxF8).

First, find a REPL! Here’s a list. The fact that almost every serious programming environment has one should tell you REPLs are a serious tool you need to master.



* Python:
    * [Default Python shell](https://docs.python.org/3/tutorial/interpreter.html)
    * [IPython](https://ipython.org/documentation.html)
    * [bpython](https://bpython-interpreter.org/)
* Ruby:
    * [irb (Interactive Ruby)](https://docs.ruby-lang.org/en/2.7.0/IRB.html)
* JavaScript:
    * [Node.js](https://nodejs.org/api/repl.html)
    * [Browsers' Console](https://developer.mozilla.org/en-US/docs/Tools/Web_Console)
* Lisp/Scheme/Clojure:
    * [GNU Clisp, Scheme, and other implementations](https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref.html)
    * [Clojure](https://clojure.org/guides/repl/introduction)
* Scala:
    * [Scala REPL](https://docs.scala-lang.org/overviews/repl/overview.html)
* Haskell:
    * [ghci](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/ghci.html)
* Java:
    * [jshell](https://docs.oracle.com/javase/9/jshell/toc.htm)
* Groovy:
    * [groovysh](http://groovy-lang.org/groovysh.html)
* Erlang:
    * [erl](http://erlang.org/doc/man/erl.html)
* Elixir:
    * [iex](https://hexdocs.pm/iex/IEx.html)
* R:
    * [R documentation](https://cran.r-project.org/doc/manuals/r-release/R-intro.html)
* Perl:
    * [perl debug mode](https://perldoc.perl.org/perldebug.html)
    * [re.pl](https://metacpan.org/pod/Reply)
* Lua:
    * [Lua documentation](https://www.lua.org/manual/5.1/)
* Swift:
    * [Swift REPL](https://docs.swift.org/swift-book/LanguageGuide/InteractivePlayground.html)
* Go:
    * [gore (third-party)](https://github.com/motemen/gore)
* Julia:
    * [Julia REPL](https://docs.julialang.org/en/v1/stdlib/REPL/)
* OCaml:
    * [utop](https://github.com/ocaml-community/utop)
    * [Default OCaml REPL](https://caml.inria.fr/pub/docs/manual-ocaml/toplevel.html)
* F#:
    * [fsi](https://docs.microsoft.com/en-us/dotnet/fsharp/tutorials/fsharp-interactive/)
* C#:
    * [C# Interactive Window in Visual Studio](https://docs.microsoft.com/en-us/visualstudio/csharp/csharp-interactive-repl-in-visual-studio)
    * [csi command-line tool](https://github.com/dotnet/roslyn/wiki/Interactive-Window)
* Kotlin:
    * [Kotlin REPL](https://kotlinlang.org/docs/whatsnew13.html#kotlin-repl)
* Crystal:
    * [icr](https://github.com/crystal-lang-tools/icr)
* Rust:
    * [evcxr (third-party)](https://github.com/google/evcxr)
* PHP:
    * [php -a](https://www.php.net/manual/en/features.commandline.interactive.php)
* MATLAB:
    * [MATLAB documentation](https://www.mathworks.com/help/matlab/matlab_env/enter-statements-in-command-window.html)
* Shell scripting languages:
    * [Bash, Zsh, and other Unix shells](https://www.gnu.org/software/bash/manual/bash.html)
* Racket:
    * [Racket documentation](https://docs.racket-lang.org/drracket/interactive.html)
* Prolog:
    * [SWI-Prolog and other implementations](https://www.swi-prolog.org/pldoc/doc_for?object=section(%27A.14%27))

Let’s talk about what you can do with REPLs and why you would want one. I’ll start with the ordinary stuff and work my way up to the [definitely controversial radioactive-spider use cases](https://thedailywtf.com/articles/The-System). 

You write some code. You compile it (for most languages). You run your unit tests. Or you run a small script to check things. Or you start up your system and then manually interact with it to check if your code works.

This cycle takes time. You spend a lot of time just waiting for compilation to complete, or the unit testing framework to boot up and then run your tests. Each individual delay might take a few seconds, but it all adds up. Pretty soon you’re on Twitter or Youtube or Discord and you lose twenty minutes before you drag yourself back to work. There are decades worth of articles all over the web about the productivity horrors of “context switching” and how it kills your concentration. You work best as a developer when you can get into that [magical “flow state” where the code just pours out of your brain](https://www.ted.com/talks/mihaly_csikszentmihalyi_flow_the_secret_to_happiness).

You know what makes flow state easy to maintain? A REPL! You write, you see results. [Right here, right now](https://www.youtube.com/watch?v=ub747pprmJ8). If that REPL is exposed by a local version of the system you’re working on, it gets even better. You stay in flow working on your system. Out pours the code!

Now let’s take it to the next level. If you rig up some sort of hot-reloading into your system (there’s always a way to do this in your language of choice), you can debug in flow state without those restart wait times that break your concentration. Instant, instant feedback. [That’s the drug we need](https://www.youtube.com/watch?v=uASnEyRG_uQ).

You run your code in the REPL. It’s broken. Alt-Tab back to your editor, make a change. Alt-Tab back to the REPL. Hit the up-arrow key, hit return, see results. Still broken? Repeat. Fixed? Alt-Tab, run the unit tests and [bask in the glow of green](http://www.extremeprogramming.org/rules/unittests.html). Go grab a coffee. [You’re a 10X dev](https://gwern.net/doc/cs/algorithm/2001-demarco-peopleware-whymeasureperformance.pdf).

The feeling of intellectual reward you get from not wasting any time waiting for the machine is not something you’ll ever want to give up on again once you’ve tasted it.

And up another level. Take your REPL and start adding custom commands and shortcuts. Your command history will have lots of code that you keep using again and again. Turn that repeated code into shortcuts. Apart from the righteous satisfaction every dev gets from [building their own tools](https://www.cs.cmu.edu/~awb/linux.history.html), you just made yourself and your team a lot faster and a lot happier.

At this point it’s time to invoke the [eternal three virtues of programming](https://thethreevirtues.com/):

According to Larry Wall, the original author of the Perl programming language, there are **three great virtues of a programmer**; Laziness, Impatience and Hubris



1. **Laziness**: The quality that makes you go to great effort to reduce overall energy expenditure. It makes you write labor-saving programs that other people will find useful and document what you wrote so you don't have to answer so many questions about it.
2. **Impatience**: The anger you feel when the computer is being lazy. This makes you write programs that don't just react to your needs, but actually anticipate them. Or at least pretend to.
3. **Hubris**: The quality that makes you write (and maintain) programs that other people won't want to say bad things about.

[[“Programming Perl"](https://www.oreilly.com/library/view/programming-perl-4th/9781449321451/), 2nd Edition, O'Reilly & Associates, 1996]

Nothing is more faithful to the Three Virtues than a REPL (or perhaps [Perl](https://www.perl.org/), or [Raku](https://www.raku.org/) nowadays, I guess).

Your shortcuts can go beyond mere abbreviations. All large systems define conceptual abstractions for the system itself. How do you represent data? Operations on that data? Components? User interactions? API requests? All these things and more can be “[reified](https://ericnormand.me/podcast/what-is-to-reify-in-software)” in the REPL. Given form and substance, so that you can use them directly.

Here’s an example. Let’s say you use an [Object-Relation-Mapper](https://opensource.com/article/17/11/django-orm) to talk to your database. Maybe your code looks like this:

```

let alice = new User({ name: “Alice”, role: “admin” })

alice.save()

```

You could type that into the REPL directly. Or you could lift up (“reify”) the “idea” of data entities into a system of shortcuts:

```

> save user {name:alice,role:admin}

```

Here’s another example. Let’s say you’re working on a REST API. Sure you’ve got [curl](https://curl.se/) and [postman](https://www.postman.com/) to help with testing and debugging, but again, they do require a context-switch. Not much, but it adds up. Instead, use the REPL:

```

> GET /api/users

[ …list of user objects… ]

```

Once you start to build up a set of shortcuts, they become a [vernacular](https://tek-ritr.com/its-more-than-chaucer/) that makes you and your team very happy developers. And fast.

Where can you go next? I’m sure you’ll be very familiar with [code working perfectly on your local machine](https://blog.codinghorror.com/the-works-on-my-machine-certification-program/), and breaking once deployed to a build server, or to staging or production. This is normal. No amount of testing will save you. Indeed the purpose of build or staging servers is to find exactly these fracture points where different environments break your code.

That’s nice. What isn’t nice is the pain of debugging code that is broken on staging. Welcome to log file hell. We won’t wade into the Big Fight about [print-versus-debugger](https://buttondown.email/geoffreylitt/archive/starting-this-newsletter-print-debugging-byoc/) debugging, but when it comes to staging servers, all you often get is a log file.

So you add some print statements and you redeploy. Then you wait for the remote system to churn. Then you load up your log files (probably in a clunky web interface), and try to make sense of the output, most of which is utterly irrelevant. You thought flow state was hard to maintain for local dev work? There ain’t no flow state here. This work is grueling and slow and very not fun.

Wouldn’t you like a REPL into staging? With a REPL you just look at things directly and muck about debugging right on the system, with the staging data. This is the way to debug! And if your REPL also includes some commands for data querying and modification, then you don’t even need to Alt-Tab over the database interface. Bugs and issues on staging can be resolved in nearly the same way, and nearly as fast as local issues. Happy happy developer!

When you implement a REPL to give you access to staging, you may not always just be able to expose an open network port, which is the normal mode of connection to a REPL. For things like serverless functions, you’ll need to co-opt the cloud infrastructure to let you submit commands and get responses. Use things like AWS Lambda invocation to get your commands up into the cloud. What matters is the developer experience of an interactive terminal. What happens behind the curtain is an implementation detail. 

At this point you will need to start thinking more seriously about access control. Staging servers, build servers, and test servers are shared infrastructure. Who gets REPL access and what can they do? That has to be context dependent, but here’s some things that have worked well for me.

If your system has a concept of user accounts with permissions, reuse that abstraction. Each REPL is operated as a user in the system. You’ll need to add some way to login or provide an access token. If you have an external API that provides this already, you’re nearly home and dry.

If you don’t have a concept of a user, then you’ll need to at least provide a set of access levels, and restrictions for those levels. This is no different from other kinds of infrastructure work that you do to make your systems easy to monitor and maintain.

You will also want to control access at a high level such as network layers and application domains. This is again very much context dependent, but things like [kubectl](https://kubernetes.io/docs/reference/kubectl/kubectl/) take you most of the way there.

And here’s the big one: TURN OFF THE REPL IN PRODUCTION. You really don’t want an active REPL to go live (well…unless you do–see below!). The safest way to do this is to treat the REPL as being under feature flag control, switched off by default. Add some failsafes by also checking general environment characteristics, like system variables. A REPL falls into the same bucket as system monitoring and control, so you can apply the same DevOps control principles. 

And finally, you can run a REPL in production. It’s not as crazy as it sounds. A REPL lets you resolve emergency production issues. It gives you a fine-grained administration interface. It lets you perform maintenance you have not yet automated. These are all massive time savers, especially in the early days of a startup, before you have full systems in place.

Access to the production REPL should be very tightly controlled. And I like to add in additional layers of security, such as time-based one-time password verification. You could even start using SSL certs if you’re really paranoid.

I have found that the operational benefits for power users are worth the risk.

There is one important thing you’ll need to add to your REPL implementation if you go all the way to production. An audit log. You’ll need to keep a log of all the commands, and who ran them. And you’ll need to add some scrubbing logic to remove sensitive data. Your REPL is now a serious systems management tool, so will require more effort to maintain.

I’ve been using REPLs in all these ways for more than a decade. A REPL makes me faster and happier as a developer. It will make you faster and happier too - use a REPL!