Hacker Read top | best | new | newcomments | leaders | about | bookmarklet login
On Repl-Driven Programming (mikelevins.github.io) similar stories update story
394 points by todsacerdoti | karma 157974 | avg karma 11.55 2021-01-03 01:35:08 | hide | past | favorite | 210 comments



view as:

AFAIK both Lisp and Smalltalk environments are image based. That is, all source code and all objects are stored in memory. You need to save that image in order to continue from where you left off. Snapshots are also used to allow rollback to previous "good" states.

> That is, all source code and all objects are stored in memory.

Common Lisp usually stores code in form of normal source files that are then possible to load into a clean-slate Lisp image. It's possible to dump images and restore them, but it's not the norm of working with CL.

Unlike in Smalltalk, I currently know of no tools that allow one to easily edit source code of functions/methods that have already been compiled into the system, and therefore the dependency on the filesystem source files is heavy and immediate.

AFAIK dumping images in CL is mostly used for shortening load times by preloading code and data, and for application delivery, but not for live editing support.


It's not compiled, but Ruby's Pry REPL allows you to edit and update code files during live execution. I use it all the time for adding breakpoints and live fixing issues. It's very productive for problem solving.

> Unlike in Smalltalk, I currently know of no tools that allow one to easily edit source code of functions/methods that have already been compiled into the system

Lots of Common Lisp systems support that in some way. Those record the location of the source code together with the machine code. These locations are stored in the image.

For example the standard CL function ED takes a function name and in many Lisp systems this will edit the function in some implementation specific way. In Emacs one uses M-. to get the definition of a function. In those implementation it will ask the Lisp for the location of that function source code.

The one system that worked similar to Smalltalk is Interlisp. See https://interlisp.org . It recently has been open sourced and is in the process of making it more accessible. It's a glimpse into an alternative world of computing from the past.


I know that many implementations record source location, but the source itself is still on the filesystem, not in memory, therefore making source locations useless if we move the filesystem out of the equation.

I also know that many implementations store the forms in FUNCTION-LAMBDA-EXPRESSION, but as I said, I know of no easy way to edit these in-memory forms either. Interlisp has had a structure editor to work with those, but that utility was not ported back into the CL workflow. I hope it eventually will be, with Interlisp being open sourced now.


> therefore making source locations useless

Just make source available on the file system. Mount it. Copy it.

That's why Common Lisp has logical pathnames. Typically one uses logical pathnames in source locations. When I set up my application on a machine, I define a translation table to point to the source code.

One can also just use standard pathnames and mount the source code to a standard path. That's how one also has set up Lisp systems in clusters. Every machine mounts the source in a standard path (or a logical path) and a client system will then have access to that source code. The image still has the recorded locations.

Btw., even Smalltalk stores its source code not in the image. The Smalltalk source code is stored OUTSIDE of the image in the file system and it has to have the files available and needs to know the location of these source files.

See: https://squeak.org/downloads/

    The Squeak/Smalltalk programming system consists of three parts:

    * a virtual machine for your platform,
    * both image and changes files of a particular version, and
    * a sources file for the particular image file.
The source and changes files contain the source code of the running Smalltalk. An edit of source code will then lead to an entry in the changes file.

Smalltalk usually can decompile byte code, so one can edit code which has no corresponding source code, with some loss of original source information.


One can redefine functions on the fly in Common Lisp. One can retain the source associated with compiled functions (even if the Common Lisp doesn't natively support that, by augmenting DEFUN via the macroexpand hook). I have done just this in an in-core mutation testing framework for Common Lisp, which creates and tests hundreds of mutant versions of a function, automatically.

In general though, there's little good reason to edit Common Lisp code in core. Common Lisp is a language with structure at the character level (reader macros); reading in a form loses all this. This is unlike Interlisp, where everything, even comments, were s-exprs and could be edited in memory with sedit.


Dart, Java and .NET have a kind of edit-and-continue without images.

It is all a matter of tooling.


> It is all a matter of tooling.

I think the point of the article is that tooling can only get you so far. The abstraction they attempt to provide is leaky if the support for this style of development isn't firmly designed into the entire system.


Yeah, but in that regard developer culture is what matters.

I have seen very few people actually using these kind of workflows back when Smalltalk and Lisp were more relevant (I used Smalltalk/V back then).

It is like using gdb, many don't go beyond step, next, print, run, breakpoint and discover how powerful it actually is (same applies to other debuggers).


I am indeed claiming that tooling only gets you so far, if you don't have good support designed into the runtime.

I take you at your word that you didn't use the sorts of workflows I've described, but I did, and I still do, to the extent that I can, and it was common place among, for example, the programmers working on the bauhaus OS or the ones working on the SK8 development environment.

We kept images around as a matter of course for various purposes, for example.


Note that modern Smalltalks allow exporting code out to plain text files so one can use Git and code review tools just like any other language.

For what it's worth, IPython's "autoreload" magic and Julia's "Revise" package get you _closer_ to what the author describes. In both cases, one can define functions/classes/structs in a file/module, load it in a repl, create objects etc, modify the file and have the changes propagate through to live objects

You can also add breakpoints which automatically occurs when exceptions are raised. Which is similar to the breakloop functionality mentioned in the article.

Yes, running pytest with the --pdb flag will drop you into the debugger on an unhandled exception which gets a comparable workflow but it's not quite the same as, a) writing tests to file first is not repl-driven development, and b) you generally have to think about doing it first.

In an ideal repl-driven world you could write the test in the repl entirely and commit it to disk once you're ready.


I find ipython + ipdb super useful in this regard

But AFAIK neither provide the feature described in the article where you can continue running after an unhandled exception.


True, but running the %debug magic command in IPython immediately after an unhandled exception drops you into the debugger at the point of the raised exception, complete with the full interpreter state including the stack. It doesn’t permit edit-and-continue but you can still evaluate expressions (including calling any function) to explore what went wrong without having to recreate the situation inside an explicit pdb session.

Closer but still so far. As soon as you have more than one module in Python it completely falls apart, despite any autoreload magic. It just isn't the same at all as REPL based programming. In Lisps you can write your entire program with an instance and a REPL running the entire time. No restarting the instance required. It works because it's just how Lisp works. A REPL is a trivial thing you can write yourself that runs in the same instance.

Whenever I think about it I can't quite put my finger on exactly what Python would need to make it the same. It's something to do with the way imports and namespaces work, though.


> Whenever I think about it I can't quite put my finger on exactly what Python would need to make it the same. It's something to do with the way imports and namespaces work, though.

Python has a fundamentally broken module system, and Python 3 did nothing to fix it:

https://nedbatchelder.com/blog/201908/why_your_mock_doesnt_w...

https://docs.python.org/dev/library/importlib.html#importlib...


Right. That's it. Thanks for the links!

This article helps us understand what real repls enable that interactive language shells do not.

What would be very helpful next would be a video comparison of writing a program that is just complex enough to not be trivial or too artificial, first using the common approach, and next using repl-driven approach.


Why not just learn a Lisp yourself? You'll get far more out of it than watching a video. The old adage is still true: learning a Lisp makes you a better programmer.

Your choices are:

* Common Lisp: install emacs, SBCL and set up SLIME,

* Clojure: install emacs, Clojure and set up CIDER,

* Scheme: install emacs, racket (or guile) and set up Geiser,

* Emacs Lisp: install emacs.

You'll notice that they all involve emacs. There are probably other ways but you want something that is quite tightly coupled, which all of those emacs packages provide. REPL driven programming is better served by an editor like emacs rather than vim. You want something where you can quickly write/edit code in one buffer and send it to the REPL with a couple of keystrokes. The lower the "cost" of this operation the better; it should be as easy as typing (as it becomes that frequent of an operation).

To help you choose: Common Lisp and Clojure are the most practical. They are both general-purpose languages with a wealth of libraries available. Common Lisp, as the older language, has far more literature available including some of the best programming books ever written. Scheme is the most beautiful language and has one of the best textbooks ever made: Structure and Interpretation of Computer Programs (SICP). Emacs Lisp is the most fun, practical but also the most quirky. Luckily, learning any Lisp will put you in a better position to learn any other Lisp.

Emacs Lisp is fun because it presents the most exciting part of REPL based programming: hacking a live, running system. Most of the time you run a lisp instance just for the purpose of development, but if your program is working and doing something, then why not hack on it while it's running? There's no difference between running a REPL in a "development" instance and a live, production instance. When you hack on emacs you are doing just that: hacking a live, running instance. It's not often you get to use a tool to hack on the tool itself while it's running.

Whatever you choose, good luck on your journey. I truly envy those who have yet to experience the beauty of Lisp programming. It changes you forever.


Nice reply. In my case, I'm already an effective amateur at Clojure, but I only use the repl for testing ideas and debugging.

In the same way there are guides that teach TDD, I would enjoy seeing a RDD (repl-driven ...) tutorial.


In Clojure the "Rich Comment Blocks" are a very common way to use the REPL: https://betweentwoparens.com/rich-comment-blocks

You could have a look at this video by Sean Corfield: https://www.youtube.com/watch?v=UFY2rd05W2g It gives you a good idea of what the REPL workflow is like. Sean will give a more in-depth talk about this next week, and it will be available in the London Clojurians channel afterwards, if you're interested.

>Why not just learn a Lisp yourself? You'll get far more out of it than watching a video.

Because one is a 30 minute endeavor and the other is a weeks/years endeavor.


You can use Common Lisp with VSCode pretty easily now with the Alive plugin. It uses SWANK under the hood, but you don't NEED emacs anymore for Common Lisp. You can also use Calva with Clojure and vscode. The emacs requirement is slowly going away. You get all the perks of hacking live running systems, no emacs required.

I think the best modern example is ObservableHQ [1]. I took a screen shot yesterday when I found myself interleaving runtime, TDD, IDE, debugger and partial recompilation in one window. It blew my mind.

I have never experienced such a productive programming environment. https://twitter.com/tomlarkworthy/status/1345321532650905601...

For there I can change variable at runtime. Change the implementation and have the tests auto run. Correct the tests, set a breakpoint in the test or implementation with "debugger;".

I think it might be more productive than smalltalk because of the spreadsheet-like reactive recomputation. Plus it supports real markup as inline documentation.

[1] https://observablehq.com/@tomlarkworthy/rate-estimation


Thanks for pointing it out. The site went on my reading list.

As someone who has used CL and Clojure to write non-trivial code using REPL driven programming it makes me mad that the word REPL has been hijacked to mean "interactive shell".

I'm thankful for this article shedding light on what it really is. It's sad that most programmers haven't experienced.


I have some experience with clojure but besides using the repl as 'interactive shell' I never tried it further (for some reason I always thought it was a crippled lisp even though I did some fair amount of work in it); does it offer the full experience? I should try it again as that's something that's actually used in real life (and has a good client side cljs experience).

It does not offer the full experience. It has no breakloop and no ability to browse and edit the live running environment. It has no ability to rummage around inside the dynamic environment of a suspended function call, much less to redefine the suspended function or the types of its parameters, nor to restart the suspended call. Indeed, the JVM makes some of that stuff really inconvenient to do.

You cannot do everything from the Clojure repl in the way you can from a Common Lisp repl or from a Smalltalk worksheet.

I don't know that the Clojure language design forbids it; you might, for example, implement Clojure on top of a Lisp or Smalltalk environment and hook up their tools to Clojure through its interop. That might work, and ruricolist has been working on such an implementation called Cloture:

https://github.com/ruricolist/cloture

But existing Clojure implementations don't have the full set of tools.


The examples from Mikel are just not possible in a default Clojure setup. It does not have break loops and it does not have a dynamic object system (by design).

Ah that answers my question I guess. Thanks

Yeah, true. I would still consider Clojure a "proper" REPL driven language, though.

I do not. I have used it to write quite a bit of code, and I generally like it as a language, but I don't consider it a reasonable substitute for Common Lisp or Smalltalk. Factor, on the other hand, is--as far as I know.

I think it would help as well to list languages that have various levels of 'real repl' implementations. Wonder what modern (systems you can 'make money with') there are that support this. I know common lisp + smalltalk and worked with both and really liked them for these reasons. I miss this functionality all the time as it was far more efficient (to me!) than modern debuggers.

Isn't Python and Matlab a 'real' REPL? They are money makers.

The article specifically stated that Python does not have a real Read-Eval-Print-Loop mechanism like Lisp or Smalltalk. For example, the Python REPL doesn’t go into a breakloop mechanism for undefined functions.

The article describes specific capabilities (full program/system dynamic redefinition) afforded by a real or full REPL.

Python doesn't have that, and it's mentioned explicitly as not having that.

It's not about "existing in the real world" or "real enough to make money with".


It's not about whether they make money. It's about whether you can do absolutely everything needed to build your program interactively by talking to it while it's while it's running.

Definitions matter. A repl is a read, eval, print, loop.

That means the code you enter at the prompt is converted from a String into literal data that can be evaluated (read). The data is then evaluated to produce a result (eval), and the result is then printed (print).

If that's not what your REPL is doing then it's not a repl.

That is not what's going on in e.g. python or ruby "repls". There is no "read" step here converting the text of the program to data. The program text is simply passed to an "eval" function that produces the result directly.


This seems extremely pedantic. Python and Ruby will “read” the string inside the “eval” function.

In Lisp it makes a difference, because source code is data (other than text) and one can also compute source code in the REPL.

For example we can write a macro in Lisp and play around with it giving it code as data and see the result as code as data.

    CL-USER 1 > (defmacro while (condition &body body)
                  `(tagbody start
                            (if (not ,condition) (go end))
                            ,@body
                            (go start)
                            end))
    WHILE

    CL-USER 2 > (setf a 1)
    1

    CL-USER 3 > '(while (< a 4) (print a) (incf a))
    (WHILE (< A 4) (PRINT A) (INCF A))

    CL-USER 4 > (macroexpand-1 *)
    (TAGBODY START (IF (NOT (< A 4)) (GO END)) (PRINT A) (INCF A) (GO START) END)
    T

    CL-USER 5 > (pprint *)

    (TAGBODY
     START   (IF (NOT (< A 4)) (GO END))
             (PRINT A)
             (INCF A)
             (GO START)
     END)

    CL-USER 6 > (eval ***)

    1 
    2 
    3 
    NIL

All very nice, but in Python/Ruby/etc, the source code isn't data. So your point is only relevant to Lisp, not REPLs generally.

That's why it is not a REPL (Read Eval Print Loop), but a ReadString, Parse, Compile, Execute, Loop.

Other people are making the same point I'm about to make, but I'm going to try to clarify it anyway because, y'know, besides being a programmer, I'm also a technical writer, and I just have to scratch that itch.

Common Lisp source code (and the source code of its immediate ancestors) is not made of text strings. It's made of S-expressions, which are made of cons cells, symbols, numbers, and so on.

A text file of "Lisp source code" does not actually contain Lisp source code. It contains a text-based serialization of Lisp source code. Other serializations are possible (and there are things you can do in a Common Lisp repl to see some of them).

The "read" in "read-eval-print" means "deserialize the text into the source data that it's meant to represent".

This point is not trivial pedantry because the full power of the Lisp language is available to the read process, and can be brought to bear on how reading is done and what happens when you do it. Compilers for other languages certainly do read text strings and convert them into tree structures and so forth, but the difference is that those data structures are private to the compiler; the data structures that Lisp reads into are standard parts of Lisp's public API, as are the read function, the compile function, the eval function, the print function, and so on. It's all on the table for you to work with.

The same is true of the disposition of the s-expressions produced by the read process; you have an opportunity to bring the whole of the Lisp language to bear on those s-expressions before they are ever passed to (compile or) eval. Then, once again, what eval produces is S-expressions, and those, not strings, are passed to the print function. You once again have the opportunity to intervene in the process that produces the text serialization.

It so happens that I've spent the past six months working on an AI machine-control system written in Common Lisp, and every one of these capabilities was an important part of the work we were doing.


He's right to emphasize the properties of the systems that support interactive development with a long-running image. In Common Lisp, the difference between defvar and defparameter is a simple example. Traditional Smalltalk systems only supported image-based development. But I always preferred the moderate approach exemplified by most Lisp systems where the source code isn't overly entangled with the image state.

As a long-time but now lapsed Lisper, I never understood some people's elevation of the REPL. A command-line REPL is a poor man's development environment. If you're doing interactive development in Lisp, you'll be far more productive if you use a normal buffer with eval-defun, eval-buffer and friends. When debugging, you'll primarily want auto-updating watch expressions and object inspectors. And when you do have good use for a REPL, you'll still want it to exist within a proper buffer with persistent history, inline object inspection, etc, instead of the low-effort rlwrap terminal experience which usually passes for a REPL.

All of this holds doubly true for Smalltalk environments.


The mindset is slightly different. From a buffer one can interact with the system and it may show things inline.

The REPL, often called a 'Listener' in Lisp is an explicit dialog with the system, where the dialog is visible and parts of the dialog can be reused and inspected. Also the dialog can be suspended temporarily and we interact with the running code itself in break loops until we resume the original dialog in some way.

This usually is the horror for people wanting predictable code - where in an interactive Lisp, one can change code interactively at runtime.


Why not all of it?

Long ago, I was doing Objective-C development with an embedded F-script REPL, hot code swapping, and XCode's built-in debugger. It was very nearly as nice as Smalltalk in many ways, only without the whole IBD situation.


that doesn't sound nearly as nice as smalltalk

You misunderstand me. By "repl-drive programming" I do not mean programming that is fixated on the "command-line repl". On the contrary, what I'm talking about has much more in common with what you describe as "far more productive". It's not about a particular shell or window or buffer; it's about a runtime environment that is designed to support writing software by building and changing it as it runs.

By "repl", I do not mean the window or the buffer or the shell program. I mean the loop of: read an expression, evaluate it, and present the results, in the context of a runtime that is designed to comprehensively support it.

Moreover, you can have all of the tools that you listed and still not be working with a properly-designed repl-driven environment. For example, I built a 3D interactive environment on the JVM that worked quite well--it launched into the 3D environment ns no more than a second or two, and could build whole procedurally-generated scenes in a few seconds. It supported networked multiuser interactions. I could start it from a repl and dynamically alter scenes and objects and their behavior in the environment by talking to them.

It still wasn't a proper repl-driven environment because the underlying runtime could not correctly handle dynamic redefinition of classes and methods. That meant that if I decided that I needed to change a representation or something, I had to kill the environment and rebuild it. It meant that there was always a gratuitous barrier that I might run into at any moment. It meant that some abitrary set of things I was working on was always on the other side of that barrier.

Contrast that to working with, for example, SK8, where I could redefine absolutely everything in the environment (including, for example, the system-level procedures used to draw window frames) without ever restarting the code that was under development.


Is there a modern non-Smalltalk, non-Lisp (or its derivatives) non functional imperative programming language that supports a "real repl" that the author is talking about? I don't know if that kind of environment is for me but I'd love to give it a spin.

I have never found the REPL (the Python/Ruby REPL which is not a "real repl" according to this article) to be that useful beyond quickly playing with API of a library with sample data and getting a feel of it and its return types. So this version of repl described in the article does sound interesting.



I’m thinking a SQL console mostly qualifies per the article’s idea of a repl. The break loop concept isn’t perfect there though.

I’ve always liked the python repl, breaking out a django repl and being able to manipulate the db via the api always felt like a super power to me when coupled with the expressiveness of basic python but last year i started dabbling with Clojure. My first “real” repl driven development experience.

2 things stood out for me:

1. You end up with some genuinely useful executable documentation and it just makes sense to me to preserve some of what i did in the repl while developing. I saw this first here: https://github.com/stuartsierra/component/blob/master/dev/ex...

2. Integration with your editor is essential - having a keystroke to send a form to the repl or evaluate in-line is a real productivity boost. It’s like writing code while an intelligent debugger is permanently live. You don’t need to wait for a compiler run then parse the feedback, it’s just instant in-line runtime feedback. “Paredit” is really sweet. I used calva in vs code for this and tbh it’s as attractive as the language in some ways. I have no idea how to meaningfully apply those concepts to something like python or javascript though. They just don’t lend themselves to that kind of structural editing.


have you ever tried something like https://gtoolkit.com/

There's Hy (hylang.org) for Python which comes with a real repl.

Bash?

Shells in general are a great example IMO. They are what I'd maybe call repl-first environments, in that they are used primarily as interactive repls but can be used to write programs.

In Emacs you can set up Python in a (quite neutered) similar way. Whenever I change a function, I just evaluate the whole buffer to load it in the attached Python process, and will switch over to the Python process to play with it if need be.

Also, Forth sports a repl.


Julia certainly places a lot of emphasis on the repl. It doesn't have break looks that I'm aware of although I really wish it did!. It's a specialty language though IMO, if you're not doing something scientific I'm not sure it makes much sense to learn.

For those of us that don't use Lisp (or Emacs+SLIME), and are stuck with Python/Julia/Lua/etc and Vim may I give a practical recommendation? The Vim-Slime plugin [1] is a half-decent way of making interactive development work. With a bit of tuning you can make it start a terminal emulator window inside Vim, and have it send the current paragraph of your source code into it with a press of a button.

While not as great as working with a proper Repl-oriented language (as the article explains), this is still so much more pleasant than having to re-run the whole program every time you change a function.

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


I also recommend jupyterlab for a similar experience. I was a hardcore vim terminal guy for 17 years, but now I won’t be going back.

The ability to interactively develop functions in almost any language inside the same environment had been revolutionary for my productivity given my short attention span.


If you're using Jupyter within the browser you can have both via the FireNVim plugin in Firefox. It connects to NeoVim in the background and lets you edit any multi-line text field with 100% of your personal Vim configuration, the only limitation is that the color scheme cannot be changed.

FYI I use firenvim daily and it can certainly change color scheme. Is it only when used with Jupyter that color scheme can't be changed?

Would you mind sharing what you're doing with JLab? Do you use it in machine learning or are you doing something completely different?

Some amount of data science, some amount of node development of various kinds. Only about 10% ML work.

> With a bit of tuning you can make it start a terminal emulator window inside Vim, and have it send the current paragraph of your source code into it with a press of a button.

Defining keybindings for splitting the window, opening a terminal buffer and copying a paragraph does not really require a plugin. That's one of the features I like most in Vim, its ability to just map any input to another sequence of keypresses can replace whole scripts and plugins provided you're fine with readability comparable to regular expressions.


And nvim-R for vim/nvim and R.

[1] https://github.com/jalvesaq/Nvim-R


Without caring about VIM, this would be my options,

Python IDE tooling on Microsoft stack.

https://docs.microsoft.com/en-us/visualstudio/python/python-...

Juno IDE for Julia

https://junolab.org/


Another way is ipython %edit magic command and $EDITOR set to vim and using %autoreload

https://ipython.org/ipython-doc/3/config/extensions/autorelo...


Writing a ray tracer in Common Lisp is an excellent video that helps one understand how live editing aka REPL driven programming works with a Lisp such as CL that supports it from the ground up.

[1] https://www.youtube.com/watch?v=N1oMRw04W3E

edit: fix typo


REPL is very useful, but few months ago I realize[0] that setting a break-point is not easy as people think, because the program can visit the line through multiple paths(function can be a call from different places), so you can spend a lot of time debugging/finding the right path to the line, which means that you probably have to set more break-points to get the program to the right place faster.

[0] https://skyalt.com/blog/repl.html


In lisp I do something like:

  (when (in-state-i-care-about)
    (break))
Many non-lisp debuggers have conditional breakpoints, though they often have a larger performance overhead than the above.

This reminds me of Unison's "codebase manager": https://www.unisonweb.org/docs/tour#-the-big-technical-idea

It's not exactly a repl, but it shares similarities to the idea of interacting with your source code itself via software, not just by typing bytes into a file.


Nobody has mentioned low-level programming in a REPL. Not as common to be sure. Forth works this way and even has REPL Assembly Language. It's been there for over 40 years.

Testing Forth and ASM code snippets in the REPL before committing to them helps eliminate those nasty assumptions about what "should" work.


Agreed, FORTH is repl-driven in the sense I mean. The main difference from Lisp and Smalltalk systems is that FORTH environments are, generally speaking, more spartan.

In the late 1980s I had a group of friends at Apple that included Smalltalk, Lisp, and FORTH programmers. We certainly found plenty of things to admire and attempt to steal from one another, and everyone accepted the basic goodness of building systems by engaging in conversation with them as they run.

Lisp and Smalltalk cross-pollenated each other more than each did with FORTH, but maybe Slava Pestov's Factor is a glimpse of what you get if FORTH is more in the mix.


Yeah, Factor's environment looks really similar to something like Smalltalk. The thing with Forth is that it looks really daunting to perform higher level tasks that I'd be interested in performing on a day to day basis.

It can definitely be done, but historically, that's not really been its sweet spot.

Forth's answer to your need is: Write your own higher level utilities in Forth. :)

I find it better to think of Forth as a clever Macro Assembly language for a two stack VM.

When used as designed, one does not actually program in Forth. You first write a tiny "language" and program the App in that.

Not a popular choice today.


Oh, I forgot to mention that one of the cool features of Macintosh Common Lisp was its interactive assembler. You could write "LAP" ("Lisp Assembly Program") code in the repl, just like you could write Lisp code.

Around the time that MacFrames morphed into SK8, Apple hired an arcade-game programmer named Dave Vronay to work on the SK8 graphics subsystem. When I first met him, he was over the moon about MCL and how great it was to write assembly code in a repl and run it right now.


I had no idea this was possible now, and to find it was commonplace for decades makes it even more amazing, and worrying what else I've missed. Thanks for sharing this here!

Be prepared to be amazed with what Xerox PARC and others were doing, while Bell Labs was busy pushing for UNIX.

"Eric Bier Demonstrates Cedar"

https://www.youtube.com/watch?v=z_dt7NG38V4

"Emulating a Xerox Star (8010) Information System Running the Xerox Development Environment (XDE) 5.0"

https://www.youtube.com/watch?v=HP4hRUEIuxo

"Documents as User Interfaces Video Demo"

https://www.youtube.com/watch?v=0-_zVkrWCOk

"SYMBOLICS S-PACKAGES 3D GRAPHICS AND ANIMATION DEMO"

https://www.youtube.com/watch?v=gV5obrYaogU

"Alto System Project: Dan Ingalls demonstrates Smalltalk"

https://www.youtube.com/watch?v=uknEhXyZgsg

"Action!, the worlds first dynamic interface builder - 1988" (Interface Builder percursor, written in Lisp)

https://vimeo.com/62618532

"The Interlisp programming environment"

http://larry.masinter.net/interlisp-ieee.pdf

And the cherry, how Lucid used Lisp ideas for their Energize C++ IDE, including an image based format for AST storage

https://www.youtube.com/watch?v=pQQTScuApWk

https://www.dreamsongs.com/Cadillac.html


These lock-in IDE environments, while impressive, thankfully never got really popular.

I find superior languages like Lisp or Standard ML most enjoyable in text files.


Look better around you.

- macOS, Windows, Android, ChromeOS

- InteliJ, Visual Studio, XCode/Playgrounds


Watching the Symbolics demo nowadays, it's amazing what they had back then.

I'd also add this talk "Lambda World 2018 - What FP can learn from Smalltalk by Aditya Siram " https://www.youtube.com/watch?v=baxtyeFVn3w

as well as the whole series of things that Tudor Girba & co. are doing with Glamorous Toolkit https://www.youtube.com/channel/UClLZHVq_-2D2-iI4rA2O8Ug

I'd still recommend folks use Pharo over GToolkit for most things, as GToolkit lacks a bunch of polish and documentation (not to say that even Pharo documentation approaches anything of the quality of the Rust and Python ecosystems).


> If it now works correctly, then congratulations; you found the problem!

This notion if “correct” is “finished the current example as intended”. Running a suite of unit tests would give me more confidence.

Even that is only for programming in the small and that is comparatively easy to programming in the large. I don’t see how a REPL would help there.

That means a REPL makes easy things easier and hard things are unaffected. Does not sound like a competitive advantage to me.


>Even that is only for programming in the small and that is comparatively easy to programming in the large. I don’t see how a REPL would help there.

Because the right way of programming in the large is programming in the small and combining things (e.g. functions, components, classes, whatever, etc.), not building some huge monolithic monstrocity (this is orthogonal to monoliths vs microservices btw).

Plus, this "programming in the large", when it gets to, say, 10.000.000 lines of code will still have bugs and behavior you need to check/search for/and fix in individual parts, and this "restarting/dynamic change" REPL will still be a great tool here.


Writing unit tests _is_ part of the interactive development style in Smalltalk. In fact TDD arose out of a lot of (ex)-Smalltalk developers. When you have an environment as mikelevins describes, where the debugger allows you to author-as-you-go, you can start by writing a test and fill in the implementation and have your test go green, all in one very tight loop.

You actually can run a bunch of unit tests in Smalltalk with a single-click, get results, and be dropped into a full authoring environment when tests fail, with the bonus that you can just fix the code and resume execution to have the test go green. Modern Java IDEs can probably do this (I've no experience with Java), but you've to understand that this was possible in Smalltalk several decades ago.


For Ruby users, pry gets you most of the features discussed in this post. You can get the error break loop, and drop into the live environment with binding.pry. Code can be added and expressions adjusted at this point. It’s very powerful and the only “debugger” I’ve ever needed for Ruby.

I've used it a good bit. When I'm using it, I miss the tools I have in Smalltalk and Lisp environments.

When I use Smalltalk or Lisp, I don't miss pry or the other tools I have when working with Ruby.

I do like Ruby, though. I probably like it better than any other language that isn't made of s-expressions. (Well, I like ML a lot, too.) But the whole time I'm working with it it just makes me want to write another Ruby implementation, but this time on top of a proper interactive runtime.

There is one, actually: MagLev, which is built on a Smalltalk runtime. As far as I can tell, though, it's dormant.


This is what makes R (and Julia I think) so special (where everything evaluates to an S-expression). Having REPL for exploratory data analysis is especially useful.

I admit I have not experienced true REPL driven development as the author defines it, but I have created an easier way for "development with a REPL" in vim. I love it because it's like a looser, faster Jupyter notebook, and it works with any language that has a REPL.

It works by sending highlighted text to vim's terminal buffer. I wrote a short blog post with a demo and the vimscript code here:

https://mcastorina.github.io/posts/vim-repl-driven-developme...


I should have read more comments before posting, but it seems I reinvented an inferior wheel:

https://github.com/jpalardy/vim-slime


For anyone who would like to see what REPL-driven programming looks like in practice, take a look at this video here:

https://vimeo.com/230220635

The author uses the Clojure REPL to walk through the process of developing some non-trivial functionality (calling an API and parsing the results).

It’s a good intro to what it looks like to interactively build a program while it’s running.

Personally, I’ve found that having such a tight feedback loop makes development a lot more enjoyable.


Interesting. I do the same thing in Elixir where I'll attach an iex session to a Phoenix application so I can interrogate modules and APIs as I'm building them out.

I'm slightly disappointed that it's already something I do day to day. I had hoped that the power of the REPL wasn't overstated.


It's nice to be able to do this in production for debugging. It's super powerful and one of the my favorite features of erlang.

It's especially handy for debugging IoT and hardware issues.

I think just because someone is able to do something similar in another language doesn’t mean that the power of a fully integrated REPL is overstated.

Most developers are using languages where this sort of thing isn’t possible, and for them, experiencing a REPL driven development flow can be an eye opening experience (even if it’s just to add it to their tool box along side more common approaches like attaching debuggers and using TDD to shorten the development feedback loop).

I don’t know enough about Elixir to understand how your approach is the same/different than using something like a REPL with Clojure, but I did come across a pretty interesting discussion on the topic:

https://elixirforum.com/t/what-do-you-all-think-of-clojures-...

TL;DR - you can accomplish something similar with Elixir, but the underlying technical details are different.


> Most developers are using languages where this sort of thing isn’t possible

Which language can't do this?


What are people’s thoughts on how using vs not using a REPL help or hinder one’s thinking as a programmer. Specifically I mean if you have no REPL and maybe a long compile time you’re forced to put a little more thinking and planning in up front if you don’t want to waste your time. You might be more meticulous in catching bugs. Whereas with a REPL you can throw stuff at the wall. If something breaks you can just tweak variables and structure in real time till it starts working.

That’s just one characterization I came up with off-hand and it may be inaccurate. I’m mostly trying to paint a picture where tools affect the thinking of the programmer.

What are people’s thoughts on this? Does using a REPL or not using a REPL change your thinking and how so?


> Specifically I mean if you have no REPL and maybe a long compile time you’re forced to put a little more thinking and planning in up front if you don’t want to waste your time

Amusingly, to me, this is something people describe as the difference between having to submit punch cards to (or schedule a job on) a mainframe versus having a compiler on your own machine. That having such quick access to the compiler would lead to the "throw stuff at the wall" approach.

IME, faster feedback loops do increase the "throw stuff at the wall" approach, but for those of us who still (mostly) sit back and think, it's an enabler and not (just) a crutch. I can get in the flow much better in a language like Lisp and stay there. If, for instance, there's some confusion in my mind about how a function or data structure works, in C++ it takes me longer (more of a constant factor longer rather than orders of magnitude longer) to write something and test it out (assuming documentation doesn't clear it up for me). But while the time to explore it isn't huge, I've been pulled out of my focus for longer. With Lisp, I can test it in seconds and get right back to whatever I was doing.

But also, when I don't have a REPL, I break my programs into smaller programs (libraries/modules) which can be composed into the larger program I want. This lets me, partially, recreate the REPL experience. Using a test runner (a proper one or an ad hoc one) and a set of small CLI apps that let me use the smaller modules directly I get something approaching the feedback speed of the REPL. With fewer dependencies for any part under test (or as a CLI app), I get much faster compilation speeds vs needing to recompile a much larger program.


I think it's a valid observation. Circumstances that make you think things through ahead of time compel you to learn things and gain skills that you would not otherwise learn and gain.

The other way around is true, too, of course.

But the intensely-interactive style of programming-by-teaching is the less common approach. In pretty much every thread I've been part of about the topic there have been commentators who've said they just weren't even aware of it as an option, who had no idea that full-featured repl-driven environments had ever existed or were a possibility.

What are the odds that such an obscure approach to programming has already attracted all the programmers who would benefit from it? Long odds, I'm guessing.

So my guess is that it's worthwhile to point out the option for the sake of people who would benefit from it but don't know they have the option.

For my benefit too, of course. I want more people to know about it, because that increases the chances that demand will rise. Rising demand increases the chances of greater investment in those kinds of tools, and reduces the chances of their extinction.


For people who use Clojure (or another REPL-capable language), would you say that this is the main reason why you use it?

Is it common to write Clojure or other REPL-capable languages in a more "traditional" manner (like how one would write say, Java or C)?


I'm not sure I'd say the REPL is "the main" reason I use Clojure (there are many reasons, really), but it is an important part of the experience.

I don't know of anyone who is experienced in Clojure and works in a "write-compile-test" way like you'd do in C or Java. While it's certainly feasible, it's not how you're supposed to do it.


Having used Clojure and Common Lisp professionally, I think advocates for REPL driven programming universally overstate the utility of a first rate REPL.

Yes, it’s nicer than Python’s. Yes, it’s convenient. No, I never ended up doing my development in the REPL first. Why? Because editing mistakes in a REPL usually sucks, because a REPL is not a text editor.

What I ended up using heavily was REPL to file integration, which gave me the ability to write a function normally, evaluate it in the attached REPL session, and then play around with it in the REPL. This is far short of the “REPL driven development” that’s commonly discussed, and frankly something that’s probably possible with the Python REPL if they wanted to.

Editing data and functions in the REPL is a neat trick, but it’s a double edged sword, because a REPL can crash, and it provides incredibly rudimentary support for diffing current state and migrating changes back to permanent files. I would never start with anything more than a trivial “how do I manipulate this list” in the REPL for that reason. Oh, and we had a lot of issues with REPLs getting into a bad state with Clojure due to multi methods and protocols; if you’re doing your primary work in the REPL, then having to restart it due to it becoming unstable really sucks.


I 100% agree with you and another comment: https://news.ycombinator.com/item?id=25622990

I spent years in Clojure and found that writing code in the REPL just... isn't fun. This has been true for every interactive system I've ever used (including... the shell).

The shell as a REPL for a succinct language is nice for interactive workflows, but for trying to build less ephemeral pieces of code, the editor is the first class citizen I care about. Sending code to a REPL to be interacted with? Cool, and I think this is what a lot of people mean when they say REPL-driven.

I'd imagine convenience around that in a language or its tooling would be what convinces me to do more REPL-style things.


I think what people mean when they say REPL-driven is to have a headless REPL that one never actually interacts with directly, but rather sends it snippets of code for evaluation directly from one's editor. Typing code into a shell prompt is excruciating by comparison.

I think this is a huge barrier of entry to languages like Clojure. The syntax and dynamic nature of the language really lend themselves to an extremely interactive and productive workflow, but that is a hard thing to communicate. It seems most people think that when Clojure programmers refer to the joy of the REPL they imagine writing code into Python or Ruby's REPLs (e.g. https://repl.it/languages/python3), but that is about as far from what is meant as is possible. I mean, who would enjoy programming in an environment that deleted your work every time you finished a line of code?


> What I ended up using heavily was REPL to file integration, which gave me the ability to write a function normally, evaluate it in the attached REPL session, and then play around with it in the REPL

When I do REPL driven development in Clojure, that's exactly what I do and that's what other folks that I know mean by REPL driven development (in Clojure): define types, functions and variables in a source file, often in a (comment) form, eval them one by one, and copy the code out of the (comment) form when it's stable enough. I wouldn't type code directly into the REPL; that's not a pleasant experience in Clojure - but might be pleasant in Common Lisp or Smalltalk for all I know. The process that I and others use in Clojure most definitely differs from the process that the OP describes and I lack enough familiarity with Common Lisp to know if the process is more pleasant in that language. I assume it is and I must make time to learn Common Lisp properly some day.


You don't even need the "REPL" buffer to do REPL style development. You can just use the "eval" keybinds of your editor to eval the source code in the files.

For any test/scratch code, just create a scratch file outside your project and write the experiments there, eval'ing as you type. That file could even be an "official" unit test you include in the project.


As I said elsewhere, when I say "repl-driven programming", I do not mean "programming in a repl window", or "programming at a command shell". I'm talking about the runtime's read-eval-print loop, not the UI's repl window.

When I'm working, there is little or no migrating of code from the repl buffer to a file because it's already all in a file. I almost always work in a file. I write snippets in a file, send them to the repl with a keystroke, and build up the world incrementally as I discover what it needs to be. As the contents of the file get larger and more complicated, I move things around and organize them. It's a conversation with the repl, not an editing session in the repl window.

I don't consider any Clojure tools I know of to constitute a proper repl-driven environment, precisely because the language and runtime lack support for the kinds of programming and debugging that I've taken for granted for decades. If I can't inspect and edit and redefine everything in the runtime, it's not the full, proper set of tools.

I've written a good bit of Clojure code, and I'll happily do it again if I need to do something that isn't convenient in, say, Common Lisp, but I consider it an acceptable alternative to things like Haskell and Scala and F# and Swift, not an attractive alternative to Lisp and Smalltalk.

I do occasionally need to restart a Lisp environment because of some gnarly breakage I've committed, but it's pretty rare. Moreover, killing and restarting my favorite lisps takes about--wait, let me check--okay, a second and a half to kill a live app in staging and have it back up, fully-functioning.


Yeah, outside of simple use cases, I mainly interact with the clojure REPL by evaluating forms directly from my editor. It’s about being able to incrementally build something while having it running and updated as you go.

I also tend to often just reload changed namespaces and resetting the (component/mount/integrant based) application too. Or letting shadow-cljs live reload my clojurescript. But having the REPL directly accessible is still super useful.


If your code is in a file, and you're running the code from the file, that's not REPL. that's interpreting or compiling a file. REPL is opening interactive mode (if the language supports it) and entering "2+2".

You calling anything else REPL is just wrong, misleading and confusing.


> If your code is in a file, and you're running the code from the file, that's not REPL. that's interpreting or compiling a file.

It is not “interpreting or compiling a file” if the tool you are using to evaluate code in the file is sending isolated blocks of code to a REPL and not, well, interpreting or compiling the file.

> REPL is opening interactive mode (if the language supports it) and entering "2+2".

No, a Read-Eval-Print Loop (REPL) is not exclusively used by a user typing keystrokes at a terminal; like other terminal software, it is a candidate for being driven by other programs.

> You calling anything else REPL is just wrong, misleading and confusing.

You insisting a REPL isn't a REPL when the “Read” part is reading something other than keystrokes directly entered by a human user is just wrong, ignorant, and confused.


'REPL is opening interactive mode (if the language supports it) and entering "2+2".'

If I'm going to enter "(+ 2 2)" using my normal development tools, I'm probably going to start Emacs and tell it to start a SLIME session with one of the Common Lisps I use. When I do that, it'll create a repl buffer, because that's the way I have slime configured.

I mean, I don't have to load slime-repl. I could just interact with the Lisp's read, eval, and print functions directly from an arbitrary buffer without involving the SLIME repl display at all. But, y'know, force of habit. Also, I do occasionally type something in SLIME's repl buffer, especially if I want to see some big wad of output and don't want it spewed into the middle of the expressions I'm working with.

Most likely I won't type "(+ 2 2)" in the slime-repl buffer, though. I'm more likely to type it into a scratch buffer and send it to the Lisp by hitting C-x C-e. As soon as I start typing Lisp code, I know I'm probably going to want to add some more and maybe edit it and probably send it to the Lisp again. That's just more convenient if I have it sitting in a buffer in front of me, and not scrolling off the top of the repl buffer into infinity.

So am I working in a repl? Lisp is running a loop waiting for input. I'm sending expressions to it. It's reading them, evaluating the s-expressions produced by READ, and then printing the resulting values to a stream I can look at. Sounds like a repl to me. It's what I've always understood a repl to mean, including when I'm building them.

Does it count as working in a repl when I use a keystroke to send "(+ 2 2)" to the Lisp from the scratch buffer, or does it only count if I actually physically type the text in the buffer where the Lisp displays its prompt? What if there is no prompt? What if I start Lisp and SLIME but don't load the slime-repl extension? Does that mean that the loop that is reading, evaluating, and printing stops being a repl?

I'll probably save my scratch buffer to a file at some point, if there starts to be enough text in it that I think I might forget some of it. Does it stop being an interaction with the repl the instant I save the buffer to a file? I can save the slime-repl buffer to a file, too; does it stop being a repl when I do?

What about Lisp and Smalltalk environments where the read-eval-print loop doesn't display a prompt? Take a Smalltalk or INTERLISP worksheet, for example. Is it a repl if there's a loop reading, evaluating, and printing expressions, but no prompt? Does it stop being a repl if the incremental inputs and outputs get saved to a file? Does it count if it's not a text file, but the environment automatically saves the state of the worksheet to an image file that it automatically deserializes the next time you start the environment?

I don't think I'll adopt your lexicon, but I am sort of curious where its boundaries are.


I think most Lispers reckon REPL development the way it was described; analogous to editing code in a Smalltalk Browser and Doing/Inspecting it in a Smalltalk Workspace. WRT Smalltalk, you can Do/Inspect code in a Workspace and then create a class and copy it over but this typically isn't done that much.

This is what I use as my canonical example of REPL-driven development. https://vimeo.com/230220635

The main thing that stands out to me is playing with small snippets of code in real-time as you're writing it. Contrast that with writing a class and methods, then writing unit tests, then running them. There might as much as ten minutes between the time you start writing your code and when you run any portion of it, and always running large chunks of changes.


Having never developed code professionally in Lisp, my question would be; does the REPL work flow replace test code? Because if the REPL is being used as a replacement for tests, then I can see future readers having a harder time groking the code without the benefit of test driver code to analyze.

I'd say it's more like tests grow naturally out of repl interactions.

Test-driven development as a discipline emerged from the Smalltalk community, and I don't think that's an accident. I think it's a formalization of the way that Smalltalk and Lisp programmers naturally tended to work.

I generally start on something by making a naive model of what I'm trying to accomplish and interrogating it. In successive iterations, I modify the model and the interrogations in the direction of what I need it to be, discovering details along the way.

I'd say that a majority of my actual activity is testing. That's one reason I prefer to interact with a repl from a file: because I want a record of my interactions. I want to be able to do them again and again.

The difference between that record and formal tests is a simple matter of copying the trail of my interactions into a test framework, and that, too, is how I normally work: make model; test it with interactions; keep the interactions around for later.

As I make progress, the models turn into data structures, and the interactions turn into functions and tests.


Thank you for explaining your process. I think I might benefit from this approach.

Nothing is lost. You would convert the ad-hoc test code from the repl into actual unit tests in your source code. With the added good feeling they have already been executed successfully at least once.

Potentially. But, as with all things, there's "the way you could do things" and "the way you should do things". Just because you can do manual testing doesn't mean you should avoid automated testing. It MAY be useful to do some manual exploration to find suitable test cases, but once found, they should totally be conserved in source.

It may also be faster to find an issue in the REPL, using various tracing tools ("hm, this function returns the wrong thing when I feed it X, let us enable tracing on a few things and see if anything stands out", followed by "aha, that other function over there is actually the problem, let me add some test cases, so we don't have a regression there").

None of these are impossible without a REPL, but may require extensive modification of the source and a recompilation to get the same effect (followed, probably, by either converting your tracing to debug logs, guarded by a verbosity flag, or by painstakingly track down each print and rip it out again).


>What I ended up using heavily was REPL to file integration, which gave me the ability to write a function normally, evaluate it in the attached REPL session, and then play around with it in the REPL. This is far short of the “REPL driven development” that’s commonly discussed, and frankly something that’s probably possible with the Python REPL if they wanted to.

I mean, yeah. You just import the Python module you're writing and you can call any function or inspect any object you want. iPython even has hot reload


I'm personally of the opinion that this isn't the same thing and I'm saying this as someone who wants to do this in python.

Unless you've figured it out? In which case please say, because I want to code python like this.

Specifically I write code in my editor, hit a keystroke and all the code at the cursor gets sent to my running python and evaluated. The editor needs to be aware of python's indenting rules so it can do this, but once again if you've worked out a nice way to do this please say. Minimal keystrokes required would be great =)...

I'm not really requiring that the evaluated output gets sent to the editor, though of course that would be ideal, because in the worst case I can side-by-side the editor and the terminal.

From my perspective jupyter notebooks get kind of close, but I'm not coding in a file, so it's not quite ideal.


> Specifically I write code in my editor, hit a keystroke and all the code at the cursor gets sent to my running python and evaluated. The editor needs to be aware of python's indenting rules so it can do this, but once again if you've worked out a nice way to do this please say. Minimal keystrokes required would be great =)...

...I call this "copy/paste". And since I use X clipboard and a tiled window manager, it's only a couple keystrokes and no mouse. Highlight the code in the editor, "super+<direction>" to switch windows, "shift+insert" to paste.


I don't quite agree, that might work for your specific workflow, but you can flub that sort of thing.

There's a reason I'm specifying specific actions, I can easily see increasing the cognitive burden breaking the ease of that workflow. You can't tell me that putting your cursor to a code block and hitting a key is the same as selection, copy, swap to console, paste...

I've certainly had situations where I've done what you're describing and then still taken the extra time to setup the workflow I've described above because it's worthwhile when you're trying to debug a knotty problem.


One thing that has tripped me up with this workflow in the past is that you lose context - the code you copy/paste and execute in the repl is not evaluated in its module/namespace, so any other code calling it is not updated.

Is there a shortcut to evaluate the code in the correct module/namespace for Python? Maybe to switch the namespace in which the Python REPL evaluates the code you paste in?


org-babel might work here. There are also repl keybindings in python mode in emacs.

Not a keystroke and I'm probably being captain obvious, but I use pythons execfile for this.

VSCode's Interactive mode does this quite well.

Docs: https://code.visualstudio.com/docs/python/jupyter-support-py A short demo on YouTube: https://www.youtube.com/watch?v=lwN4-W1WR84


I use a vim plugin called slimux to send code from vim into a REPL running in another tmux pane. It works really well.

Here's the fork that I'm currently using https://github.com/grusky/slimux

I use it with python and node.is and MySQL and psql

It's great because it can work with any REPL.

I used to use jupyter notebooks but almost exclusively use slimux now unless I need to some data visualisation or computer vision.

VS code's ipython REPL is quite good and has support for images and graphs.

I will say that the main thing that triggered a REPL driven workflow was dealing with large state. When you do machine learning and you have to deal with large models and datasets, loading them each time you save your code and triggering a unit test really gets tedious. You can definitely do it with caching and reducing the dataset size, but it only gets you so far IMO. There's nothing like being to inspect the state of your environment. Particularly when it takes a long time to create that state.


I've been doing exactly this for a while now using Emacs built-in python mode and I think it's its quite nice. With just a few keystrokes I can send any part of the code I'm working on into a running python process and see the results in a separate repl buffer. The built-in python-mode already has a good selection of possible selections to send and I've written two extra ones working with indent levels dependent on where the cursor currently is. [1] It's nice to be able to, for example, run the if statement you're currently writing without running the while loop that if statement is in.

One important thing to keep in mind though is that it is not as dynamic as Lisp, so things like re-evaluating a class definition does not update existing objects. I end up restarting the python process and evaluating the buffers I'm working on often enough that I've bound it to its own key.

[1] https://pastebin.com/ctz5erD4


Thanks for the definitions =)...

I've not used emacs for python, but could give it a spin for this!

Bit unfortunate that you need to restart the process for re-evaluating classes though.


Yeah. There are a quite few niggles like that. Even though you can get quite close by (automatically) sending code to the repl, Python is simply not set up for interactive development.

It's one of the main reasons why I love Common Lisp so much.

Still, it's not too bad and IMO a lot better then restarting the process on every single change


You don't have to type up stuff in repl literally. You can write regular functions in emacs and send pieces of code to the running repl.

For adhoc testing code you can type on the repl buffer.


Is there a simple way to get code I write in a lisp REPL back into my editor? That's the part missing for me and why I usually only use interactive shells (REPL or otherwise) for testing APIs or small pieces of code.

I can't imagine writing a program in its entirety in a REPL.


The standard technique is to run the loop inside the editor. This technique dates back to the 1970s.

You write it in the editor and send statements to the REPL. You're not just sitting looking at a command line. The REPL is a conversation with running state, but the conversation doesn't have to take place only through a single blinking terminal window. In proper REPL-driven development, the editor is fully integrated with the running session, and you can evaluate either in an attached terminal session or through your editor. Or by attaching whatever other tools to the running session.

Yes, exactly. It's perhaps my fault for not making this more explicit in the essay, but the "repl" in "repl-driven programming" does not mean the repl window.

Yeah, I suggest you expand upon that, or perhaps another article showing that "conversation". Unfortunately, some people - including me - have only used a repl in "one-direction" and have to copy stuff back and forth.

That's a good suggestion. Walking through a set of interactions is a solid idea.

For what it's worth, there are some videos around of people actually doing it with Lisp and Smalltalk systems, and pjmlp already posted a pile of them elsewhere in this thread.

I can add a few more:

Kalman Reti walking through some interactions with a Symbolics LispM repl:

https://www.youtube.com/watch?v=o4-YnLpLgtk

Brian Mastenbrook demonstrating Interlisp's SEDIT structure editor in the Xerox Lisp environment:

https://www.youtube.com/watch?v=2qsmF8HHskg

Rainer Joswig (lispm here on HN) showing us a little bit of repl and Zmacs interaction on a Symbolics Lisp Machine:

https://www.youtube.com/watch?v=LIGt5OwkoMA&list=PLN1hNlVqKB...

Rainer again, showing some simple interactions with Macintosh Common Lisp, which was my daily driver for years:

https://www.youtube.com/watch?v=GKG8cJl70mo

Ruby programmer Avdi Grimm shows some things that he found cool about Pharo Smalltalk:

https://www.youtube.com/watch?v=HOuZyOKa91o

Dan Ingalls (one of the original authors of Smalltalk) in a 2017 demo of Smalltalk 76:

https://www.youtube.com/watch?v=NqKyHEJe9_w

There are some other things I'd like to find for lists like this, but haven't been able to. In particular, a good demo of Apple's SK8 would be great.

If you can imagine a full-color Hypercard that could crack open and reprogram absolutely everything on the screen, including the machine code that drew the window system's widgets, all in a repl while the code was live; in which you could grab an arbitrary widget and drop it on the repl window to get a live variable reference to the widget, and then inspect it, operate on it, and reprogram it, again, while everything continued to run; in which you could build new window-system widgets by snapping together shapes and telling them to become widgets; in which you were not limited to HyperTalk for coding and text strings for data, but had a full Common Lisp at your disposal plus a Minsky-style frame system for representing data and knowledge, then you have some idea of what SK8 was like.


Back in the early 80s a friend described this approach as "programming by successive approximation." I was stung by that, but I saw his point.

Nevertheless I felt it was wrong. The approach of designing a data structure, making a few functions to manipulate it, designing another, making sure they work together, etc isn't really that different in lisp from C++. The loop is tighter when you can interactively test.

Either way you have to think ahead.


I don’t see how this is different from using the pdb Python module? You can catch exceptions and use to write functions.

The biggest distinction I see is the ability to continue from exceptions.

I find the other differences to be minor in comparison - to be able to recover and continue execution following post-mortem debugging is amazing.


How is this different from, say, programming in Python and being forced to drop into a debugger whenever there is an error? The debugger also lets you see everything in the stack, modify definitions, etc.

MATLAB and R both have options to drop you into a debugger if there is an error. Not sure if Python's debugger can do this, but other than that, this style of programming is perfectly doable outside of Lisp and Smalltalk.


So go ahead and set up that working situation and try this:

Define a function foo() in one module that calls a function bar() in another one, but don't define bar().

Now call foo().

Now, in the debugger that you break into, give a definition for bar(), and resume the execution of foo(). Don't quit the program and restart it; that's cheating!

If that works fine, please tell me what tools you're using, because they'll materially improve my tools for some work I need to do.

Also, try this:

Define some classes and make a bunch of instances of them. Write a few methods on the classes. Start your program running with code that uses those classes and methods.

Now change one or more of the class definitions. Don't stop and reload the program! Again, that's cheating.

When you land in the debugger (you do land in an interactive debugger, right?) use it to inspect the stack and find out which classes and methods are on the stack and what's responsible for the breakage. Ask the Python runtime to show you all the functions, types, and values that are on the stack, and when you find the likely culprit, ask it to take you to the source for the version that's currently on the stack.

Now, while you're still suspended in the call stack, redefine the offending classes and methods and tell Python to use the new ones. Now resume the broken computation. If you hit another problem, use the same tools to find and fix that one, too.

After you've done all that, please let me know what your Python toolchain is so that I can use it, too.

Also, if that's all perfectly doable in Matlab and R, that's good to know. Let me know about those tools, too. I haven't had much occasion to use them, but that might have to change if they work that way, too.


I don’t know about MATLAB, but everything you describe is trivial to do in R. Being dropped into the debugger is standard on most R installs (it’s an option you can turn off) and everything you mentioned can be done in the REPL.

That's good to know; thanks!

So I tried this with R:

1. Define an S4 class.

2. Create an instance of the S4 class (let's call it A).

3. Redefine the S4 class.

4. Examine A.

R complains that there's an error in slot(object, what), because A is missing a slot from the (re)definition of the S4 class.

This is promising, in that R realizes that A is supposed to be an instance of the redefined class. So far, so good.

What I would expect to see next is that either R automatically reinitializes A with the new slot (it has exactly as much information about the new slot as it had about the old ones when I created A), or, if it wants intervention from me, it presents an interactive session I can use to tell it how to initialize the new slots.

It didn't do that, but maybe I just don't have the options configured properly to make it happen.

Smalltalk and Common Lisp meet my expectations: they notice that A is an instance of a redefined class and they either reinitialize the instance automatically, or they present an interactive session that I can use to tell them how to do it.

It's entirely possible that R has this feature and I'm just missing it because I'm a noob.


My concept of a "REPL" is mostly defined by emacs. You would have a buffer with a code file, with an active jupyter kernel with the correct dependencies loaded in it. Then one would send any active region with `C-c C-c` and get timely feedback.

With this mode https://github.com/nnicandro/emacs-jupyter one can connect to a jupyter kernel running locally or remote (would mostly prefer SSH port forwarding or kubectl port-forward the remote jupyter server). It makes life so much easier to interact with cloud environment (e.g. spark).


Author describes a nice feature, but that’s not what makes repl-driven development tick for me. Repl really shines when editor integrates with it - send a form to evaluate, instantly get result back into the editor, eval a form and replace it with the value received, define and redefine the environment on the fly, get instant feedback if your code works without having to drop into shell and run some test command, micro-tests and usage examples that you can actually run inside editor to see how they work.

You can get similar integration with ruby and python, but there’s way more friction because that kind of development is not the blessed way and so nobody bothers to make it work.


I have a concern about REPL driven development, which is around how maintainable the result is.

The reason people like REPLs is because they can experiment to discover the right way to write the code in a very fast feedback loop. This alleviates a huge pain when you are operating with uncertainties or at the edge of your knowledge - 'does this function return null if no matching entries or an empty list?' - etc.

The problem is that the reader of your code is not going on that journey with you. They are coming in naively without knowing all of that experimentation you did. The writer now has the opportunity to create code that "just happens" to work by a magic of coincidences and conveniences based on very subtle non-obvious properties of the APIs and systems they are working with. The reader is severely disadvantaged and will have a much harder time to reach parity with the state of knowledge that the writer had. For example, some property of a function or object may not be documented at all, but through the REPL the writer has ascertained it is true. How can the reader know this?

So its a double edged sword in that the more you empower the original creator of the code, the greater imbalance you create to the later readers and maintainers of the code. Used well and with great discipline I could see it being very powerful and positive force to make better code. But in the worst case scenario, you'll end up with extremely unmaintainable code.


> the more you empower the original creator of the code, the greater imbalance you create to the later readers

REPL gives you more tools to shoot yourself in the foot, but you're not required to use those. Most programmers can write spaghetti code in many Turing-complete languages - not that they do that all the time.

In other words, REPL gives you powerful tools - which, yes, can be misused. They are still powerful, and can bring success when used well.


More than Lisp, SQL is a REPL-only language. The complexity you can achieve there without some interaction is very small.

After some on the field experience, my conclusion is that this is not a large problem. Yes, you were not there on the initial design and does not have all of that acquired knowledge, but you can always run your own tests and formulate and verify your hypothesis. The REPL is there for debugging too, not only for the original design.


>SQL is a REPL-only language. The complexity you can achieve there without some interaction is very small.

ha ha.

unless it's giant sql with 5 sub queries and poorly formated for bonus points

good luck


Hum... 5 sub queries? Nope, I'm talking about bigger stuff.

So REPL may make people less likely to document the code?

I don't see the correlation.


Could this issue of expressing the journey be addressed by leaving code/markdown breadcrumbs in Juptyer notebook like cells?

> For example, some property of a function or object may not be documented at all, but through the REPL the writer has ascertained it is true. How can the reader know this?

Well, the writer could document their code.

Or, the reader could discover behavior of undocumented code the same way the writer did; presumably the “reader” will also have a REPL available.


The author of the code is going to have to discover how these APIs work somehow, right? Why would the code end up worse if the author has access to a particular tool for interacting with these APIs? A shell and curl is a REPL for interacting with and exploring HTTP APIs but I don't think anybody would claim that the use of such tools makes code written to those APIs harder to follow.

In fact, one of the benefits of your REPL being integrated with one's editor, and being in the language of the system, is that the kinds of ad-hoc scripts you write when doing exploratory / debug coding which one would typically discard after use can often times simply be committed, either as utility functions or test cases. Far from pulling up the ladder on devs who come later, having a REPL makes it more likely for them to have access to the same tools used to build and understand the system in the first place.


I have worked on massive LISP projects that were developed using REPL-driven development, and your fears are critically valid. Even worse, this basically hamstrings it as a standalone application. The workflow to _use_ the application becomes "start the REPL under emacs and launch it", which is, in my personal opinion, a completely unacceptable distribution model. And if you don't use emacs, good luck to you! Once set up as REPL-based development, Smalltalk and LISP both have to essentially ship their "whole environment" to deploy the application. This only works out well for small projects that do not need to be distributed, or whose distribution you, the developer, have a lot of control over.

(There's an aside to be said about building binaries from a LISP compiler, but most binaries I have seen produced by such compilers still need access to the required libraries, which sort of hoses it as a distribution solution. This could be a result of the build system used on the projects I work on.)

And your documentation concern is valid, but I think even worse is the design concern: when designing an API, you have to stop and design it and consider the various concerns. If you develop it as you need it / as you go, it is possible to arrive at sub-optimal designs. Hacking the "next piece" out at each step is likely to lead to slanted designs with poor APIs, requiring future refactoring. You see this with programmers at every level: if you ask them to write a program to do a single thing, then add more (related) capabilities, and continue ad nauseum, at some point the program will need to be refactored. This suggests that, without prior planning, REPL-based design is set up to incur technical debt. Unfortunately, my real-life experience confirms this. Moreover, refactoring something in a REPL is... a chore, to say the least.


Your observations are valid, and need to be kept in mind when developing in an exploratory, interactive way.

Your alarm seems a little overblown. Maybe it reflects a particular bad experience? I've delivered a bunch of products built in the way I prefer to work, and it's been a long time since I've had the problems you're describing. Maybe that's because I ran into such such problems early in my career and learned ways of handling them.

And yeah, if you just iterate and explore, you can wind up in lacunae. You do need to develop some actual goal and a vision of how to get there. Interactive development can be really helpful for the exploring part, but it can't pick your goals for you.

In short, interactive development is a way of working that's good for some cases and some people (me, for instance). It's not a cure-all or an objectively superior methodology for all people or all cases. I don't want it to take over the world. I just want it to continue to be an option because it's the way I prefer to work.


Oh man, I appreciate your pragmatism and open-mindness. It is refreshing to see someone advocate for something without trying to "take over the world" and proving others wrong.

Thanks for sharing your experience amd positivity with the world.


Echoing this, I'd really like it to be an option in other languages, because it's just a pleasant way to work and it's nice to have the option.

Thanks for taking the time to respond. I wrote up my comment just to point out some of the weaknesses I have identified in REPL-based development, and how to watch out for those pitfalls as a program scales. For what it's worth, I agree with your sentiment, and believe REPL-based development should always be an option. I have a lot of success using REPL-based "prototyping", even in python, where I will quickly try/test something at a REPL before scaling it up, and having that utility to try/test/experiment quickly is often invaluable (especially if the standard build time is on the scale of minutes).

> Even worse, this basically hamstrings it as a standalone application. The workflow to _use_ the application becomes "start the REPL under emacs and launch it"

This seems like an incidental property (symptom of poor engineering discipline, which manifests itself in other ways in other development paradigms e.g. lack of documentation from "agile" teams) of the particular applications you've worked with and not an essential property of the (architecture decisions resulting from the) REPL-driven development style. What makes you think that this is actually due to REPL-driven development?

I develop several tools from the REPL and was able to easily convert each one to a standalone tool (when I attempted to do so).


The experience I have had on large, REPL-driven development is that continued development is assumed to also work under REPL-driven environments, so debugging or supporting them begins with "try typing X into your REPL." Many of the distributables I have seen also maintains REPL interactivity as part of the live deployment solution, with similar debugging solutions. While this may not be unilateral, "ship the deployment environment" seems to be a running theme.

I'm interested in your experience in avoiding this, and the process you used.


The batch-processing tools I wrote (e.g. a simulator) had CLI shims that called the same interesting functions that you would at the REPL. The interactive tools I wrote all had functions that acted as a zero-effort entry point anyway, so the conversion to standalone tool just consisted of building an image that called that function.

Additionally, my development environment (SLIME) talked with my application over a network socket, so there's no linkage between it and the application - at least, any more so than the usual problem of "Common Lisp binaries are hard to make small" but that's not specific to REPL-driven development.

The simplicity of my "solution" makes me think that we might be talking at different levels here, but I can't think of what the disconnect might be.


As stated before, the fundamental disconnect is that you are imagining a world where all subsequent development on your code is done via SLIME and sockets or some similar REPL instance. You see this as simple because you wouldn't consider developing without SLIME / sockets in the application, but consider the other side of the coin. If I am a developer who does not use SLIME / socket connections as my default development pattern, my first step to contributing to your codebase is to either (i) convert to this workflow and toolset, or (ii) develop all of the CLI shims you described, including maintaining them as functionality changes. That leaves me between a rock and a hard place, especially if my text editor of choice doesn't have good SLIME support.

Also, I do not quite buy the simplicity of the standalone tool conversion; it assumes the relevant functions are naturally well-suited as entry points, including handling malformed inputs, etc., very cleanly. In my experience, many things that make sense at a REPL in a live system need to change dramatically to mature into robust command-line tools.

As for deployment, the interactivity I am describing is exactly the linkage you mention, and it really does come down to shipping a development environment as part of the deployment environment. Shipping a binary with a swank server or similar introduces binary size issues, portability issues (you have to ship dependent libraries along with the binary), and some serious security implications. And while this may be the "lisp way", modern languages manage to avoid these issues just fine.


> But in the worst case scenario, you'll end up with extremely unmaintainable code.

Actually, isn't it far worse than that? Say you're stopped at a function call, you call it, and it does the wrong thing. You edit a variable that's passed as an argument to the function and call it again. Now it does the right thing. Great! You fixed the bug, right? Except that you don't actually know that your program can actually ever end up in that state naturally.

Where did that variable originally come from? Was it entered by the user? Was it read from a file? Was it from a table? If so, does the table contain the modified value? This seems like a recipe for problems to me. I'm sure it's fine in simple cases where you're just changing the value of a constant or something, but it seems really likely to lead to incorrect reasoning about the code while you're working with it in any but the simplest of cases. Am I blowing this out of proportion?


Well, no, you didn't fix the bug. You collected a datum about the bug: it caused a variable to have a value that it should not have had. Now you know something about it that you maybe didn't know before.

When you say "Great! You fixed the bug, right?" it sounds like a paraphrase of a passage in my essay that I had some misgivings about when I wrote it. But I thought, surely nobody's going to think I really mean the problem is definitely solved? Surely, people will realize its a bit of hyperbole.

Maybe not. Maybe I should have instead written it more soberly. Maybe I should have just said "Now you've collected a bit of data about the nature of the bug."


If there was a way to “playback” the REPL and store it for later, then it would be possible for the subsequent users (or even the same user) to see how the person using the REPL got to that point. It would not be a replacement for documentation, but it could be in addition to documentation or even a starting point.

Code developed in a REPL would ideally be "pure", meaning it takes a value and returns a value without side-effects. Then others can experiment with it to their hearts content. Maybe it's ok to read from a database, but don't write. If you write to the database nobody else will dare touch your code.

That rule absolutely does not work for me.

For me, the whole point of the repl is that I want to build something incrementally, interactively, building it a little at a time by making small changes to it. I want it in memory, running and responding to my changes as I make them. Put this here; put that there. Change that around. Let me look at it; nope. Put that back where it was. Now add this.

If the repl is stateless then the whole purpose is defeated.


I don't mean the REPL is stateless, but the code developed using the REPL would ideally be stateless. Stateless code would be easier for others to experiment with in their own REPL, without "oh no, you broke production with your REPL!"

You don't connect (necessarily) the REPL to production. The code can be stateful code if you're not careless. Just like you oughtn't log into the production database server and write/update/delete data entries directly.

This is about the development phase more than the production phase. Do something like this (directly in the REPL or in a buffer as mikelevin has described doing elsewhere):

  (defparamater *db* (connect-to-test-db))
  (query-db *db* (make-query some-query-description))
  ;; see that it pulls out the desired record
  (let ((record (query-db *db* (make-query sqd)))
    (update-record record)
    (write-to-db *db* record)) ;; update the entry
  ;; rerun the query above to see that things changed as expected
  ;; rewrite that let as a defun:
  (defun make-update-to-some-record (sqd db)
    (let ((record ...))
      ...))
   ;; test that it works
   (make-update-to-some-record sqd *db*)
   ;; see that it does in fact do the same as the let above.
   ;; move that to a permanent source file, and build it
   ;; into the system.
   ;; move tests into test file.
   ;; repeat with next feature/task
The functions run in the REPL are stateful, but we aren't careless about what they're touching.

NB: You can connect to production environments. You can even connect to live, production lisp images. This could be useful for: profiling real-world activity, debugging real-world problems, applying hot-fixes. But especially that last one should be done carefully, and ought to have been validated using a test environment first.


You can use a stateful repl to develop stateless code, but in order to provide the features of an old-fashioned Lisp or Smalltalk system, the kind of system I prefer to work with, you can't make any stateful changes off limits because there's no way to know in advance what changes you're going to need to make.

As you're rummaging around through the dynamic state of the running system, you'll discover changes that need to be made, and you might discover them absolutely anywhere. Sure, you could always kill the running system, make the change to the sources, and rebuild the system, but that's exactly what we're trying to avoid.

Consequently, old Lisp and Smalltalk systems are allergic to restrictions on runtime changes. Loosely speaking, if I find something I can't change while my program is running, that restriction is a bug in my development environment.

Old systems like this will discourage certain kinds of changes because they're usually ill-advised, but will not forbid them, because forbidding them is anathema.

As an example, several Common Lisps implement package locks on certain system packages. A package lock prevents you from changing the definitions of system-defined constructs.

But it's Lisp, so it doesn't really prevent the change. It just makes it more inconvenient. You have to say "Mother, may I?" first, which gives you the opportunity to soberly consider whether making that specific change is really really what you want to do.


Right. I usually start with creating global objects in the repl. Write little snippets in the repl to operate on these. The snippets end up with all these objects parameterized as functions. Which I can test again from the repl itself.

Turn the testing code into unit test as I go along.

Rinse. Repeat.


100%, and that also applies to the original author.

Building a script from the REPL might mean running different parts of the code out of order (such as reloading a function definition). As you mentioned, it's easy to lose track of state, writing 'clever'/unmaintainable code, and forgetting the order and purpose of the code.


I think your concern is valid, but I think it's the general case with any powerful tool: it can be abused, or shoot you in the foot, and requires discipline to apply.

I don't have REPL-driven development experience, but I think we are all familiar with StackOverflow. This is a great, great resource, but it lead scores of programmers to the "style" of development, where they mindlessly copy solutions from SO, without understanding of how and why they work.

I guess one can extend the old maxim "there is not now, nor ever will be a programming language that makes it easier to write a good program than a bad one"...

(edited grammar)


I agree with you that the immediate start-up and feedback is a great benefit to the coder. This is why I dislike complex, Rube-Goldbergian REPL systems.

There is a use-case for a throw-away interaction with a REPL. For example, how does $builtinFuncX work, or how would $data best be imported into a structure?

A REPL can also be a good initial approach to a more ambitious problem. In this case, a REPL can be good for focus and discipline.

If the second case is going to answer your concern and be constructive, it's necessary to be able to build the code for sharing and cleanly export the code for re-use.

I've had success tackling challenges using REPLs for Python and Perl [1] in both ways. But no tooling is going to solve the problem of a sloppy teammate who claims success just because "it compiles" and "it works on my box". A person who knows how to build good tooling goes further.

[1] https://github.com/viviparous/preplish


When a certain thing is done in repl, it usually converts into a function or two in the source code. The ad-hoc testing code is converted into unit testing.

That should help with maintenance no?

One big theoretical advantage I see in repl driven development is, I can be sure that _all_ the code has been executed at one time atleast. (In a normal edit-compile-link-test language cycle, I can not be sure of that)


Yep, just copy-paste a bit of REPL history for a docstring, then use `doctest` to make sure it holds. Pretty soon you'll have ~100% test coverage. Minus the error paths you didn't test in REPL.

Sure, but the point is that it requires more discipline, since the REPL can encourage you to be more lazy/sloppy.

Only if you aren't delivering something. No one (sane) is delivering a lisp image as "The Product" without also having a way to regenerate that image. If you write something in the REPL and never convert it to a proper function/class/struct/package in source, you're hosed when your system reboots and that image is lost.

This requires no more discipline than, say, actually committing files to a version control system to collaborate with others. Versus shouting from the hills, "It works on my machine!" and being confused when it turns out that you didn't commit "foo.c" and compiled it manually rather than updating your Makefile and committing both changes so others could use it.


> No one (sane) is delivering a lisp image as "The Product" without also having a way to regenerate that image.

Oh man, you are so wrong about that.

It's worth noting that this style of development also hamstrung quite a few Python web app projects in the late 90s because the Zope application server encouraged the use of this style of development (except through a browser rather than the command line) and beginners eventually got to a point that they were stuck and needed to make the leap to a completely different workflow of development based on files on the filesystem and in version control instead for their code, while content (ie. application state) for each deployment of their app remained in the embedded object database.

A frequent lament was "if you knew I would eventually have to switch to filesystem based development, why didn't you have me start out that way?"


This is how any major CMS works to this day, and anyone doing serious development does have the engineering workflows in place to replicate the database content.

You're missing that Zope application-development-through-the-browser could (and did) allow you to do just about anything with a Turing-complete template language, and if that was too unwieldy, Python script objects (which executed in a sandbox to prevent direct access to the filesystem, etc.).

Nothing forced you to a saner development model except for wanting to include 3rd-party libraries and being able to distribute and version your code.

Remember, this was when servers were definitely pets and scaling a web app meant scaling up to a bigger server, not scaling out to more servers. It wasn't immediately obvious (especially to brand new developers) that having all the code for even a simple CRUD web app, which you only ever expected to have a single deployment to a single server, live as pickles inside an object database that had a transactional history of edits, was inherently a bad idea.

A lot of interesting stuff was created that way, and integration with the facilities that were missing such as version control led directly to capabilities like versioning content in cvs and svn (since code was just another kind of content).

Eventually, Zope's so-called "Z-shaped learning curve" hindered adoption and other Python-based web-app platforms surpassed it in popularity.


Nope I am not missing that, because enterprise CMS still allow that kind of stuff.

Well, sure, but they aren't usually presented as the default path for development, but rather as an option for per-instance customization.

But if you understood my point about developing new functionality this way, why the non sequitur regarding content?

Y'know what? Nevermind. We're on the same page now.


I used the "sane" qualifier for a reason. Development like that is insane and moronic. It's unsustainable in the long term, and is not a counterargument to the idea of REPL-Driven Programming. REPL-Driven Programming does not preclude sanity and replicability and source files.

I really don't follow. If you're programming in a language without a REPL, don't you have the exact same problem? If the author doesn't document their knowledge, then it's lost for readers.

I don't really see how a REPL makes any difference there.


> The problem is that the reader of your code is not going on that journey with you. They are coming in naively without knowing all of that experimentation you did. The writer now has the opportunity to create code that "just happens" to work by a magic of coincidences and conveniences based on very subtle non-obvious properties of the APIs and systems they are working with. The reader is severely disadvantaged and will have a much harder time to reach parity with the state of knowledge that the writer had. For example, some property of a function or object may not be documented at all, but through the REPL the writer has ascertained it is true. How can the reader know this?

If these sorts of things aren't documented then not having used a REPL won't save you.

Someone ran a function with a println or in a debugger, figured out the answer, then removed the debug stuff... but did it by repeatedly compiling/running instead of in a REPL.

(Or, my personal favorite... someone didn't bother to run that particular code, but just made some assumptions, so has no idea those edge cases are even there...)


It seems like the closest thing to this in widespread use is a SQL database. If you want to change a table, you can run an ALTER TABLE command.

But of course you don't want to do this in the live, production database unless you're a DBA who is doing an upgrade manually for some (probably bad) reason.

So, you can try out your ALTER TABLE commands in a scratch database, but you'll need to save them to a migration script, and test the migration.

It seems like a weakness of this sort of live updating? Sure, you can modify your own running instance, but upgrading the production instance(s) will often still be a lot of work. Migrating a production database schema tends to be a tricky thing.


> The catch is that the designers of your language system had to think that facility through in the planning stages. You don’t get a decent implementation of it by bolting it on after the fact. Breakloops need full access to the entire development system, interactively, with a computation and its call stack suspended in the breakloop’s environment.

Interestingly, while Ruby’s default REPL (irb) doesn’t have this, and Ruby’s (and irb’s) designers obviously didn’t think this facility through in the planning stages, Ruby does have an alternate REPL (pry) which has a lot of the advanced REPL functionality irb is missing but also doesn’t have this function built-in, but which does have a separate add-on (pry-rescue) which provides it.


Having gone through Rustlings these past few days (via https://users.rust-lang.org/t/best-way-to-learn-rust-program...), I feel like the speed of the Rust compiler has gotten a lot faster since I looked at it a few years ago.

And the compiler messages (once you get the hang of it; and the investment in learning the syntax is so trivially worth it in the long run) are so much richer and more helpful than other languages. So it really does "feel" like a REPL or most of the way there as a REPL (and I have one of the few wikis at our work advocating for Common Lisp that I wrote a year ago, and still actively follow Clojure groups, etc.).

It's just Rust starts and runs so fast (and Cargo makes it easy and clear like Go); so that being able to re-run periodically really fast is like a REPL. Yeah there's no retained state or ability to keep a REPL running for days, etc. (though Tmux.. and nix as a repl.... more on that later), but it's reached a point where compilation is an OOM faster than before, so the loop of: read, eval, print now can include the compiler.

And you get this amazing boost where if you're operating e.g. on large data (if you're a senior engineer, like Dan Luu blogs https://danluu.com/, you're often scanning codebases or log files or other metadata to answer questions); so being able to tweak and re-run even without caching at OOM faster speeds than other languages is wonderful.

I can create complex refactoring scripts in Rust on GBs of data and constantly re-run them until it's just right.

To my knowledge you can learn the ins and outs of CL or Rust; both fairly specific-knowledge-heavy languages at first (Rust with some syntax, CL with its non-uniformity of functions). It just seems like Rust is the future. It sort of "merges" dynamic languages with REPLs and *nix (with tmux) as the REPL. Pretty exciting.

And if someone wrote a CL-like REPL with something like SLIME/SLY for debugging for Rust it'd be kind of game over. And I could see that coming in the next few years (https://github.com/google/evcxr seems cool, but perhaps the dependency on Jupyter complicates it a bit).


I've been doing debugger driven development my entire adult life and consider it a bad habit. I'm much better off and happier when I plan out and compile in my head. Then I get a good feeling when it compiles and runs the first time. If not, then I need to think about it some more, not sit in the debugger trying things out.

Question by someone who never used interacted development before: How do you save the results of your work? How do you ensure the state of your program is what you think it is.

E.g.: Imagine you're in a breakloop as described in the article. You find that local variable X is 5 when it really should be 4, so you quickly set it to 4. You find function foo() is not defined, so you define it. You continue and your program works. Great!

At the end of the day, you quit your environment and shut down. How do you ensure your interactive work is not lost and the environment is still what you expect it to be when you start again the next day. How would you compile such a program?

Is there some command that dumps the whole environment to a file as source code? Would you save your REPL history? Would you manually copy/paste relevant bits to s code file?

Also, if significant parts of the source code are written inside the REPL, wouldn't the lack of modern IDE features be a hassle? No syntax highlighting, no code completion, no code inspections etc. Or are there tools that offer those?


The way I do it with Clojure, you never really write any code in REPL prompt directly. You write it in your source code file, then send a piece of code to the REPL to update the “in-memory” state of your running program.

So any modifications you make stay in your files, just like you would normally do with any other language. But you write your program while it’s running, and you grow/change it piece-by-piece, until it’s done (by that time, your source code is exactly your finished program).

If you want to re-start your REPL session, you just run your current file in the REPL as a whole, and maybe run some “Rich comments” (from the same file) to set up a limited test environment (to isolate the piece of program you’re working on).

This way, you make use of syntax highlighting and all the IDE features.

Edit: I’ve re-read your example with breakloop — I don’t really have experience with this style of REPLing. I agree that it raises a lot of questions, but thankfully functional nature of Clojure discourages this kind of programming.


You can edit the source file and send it to the listener.

Suppose I'm in my REPL and I do this:

  > (foo 'bar)
I get dropped into the debugger because foo doesn't exist. So I switch over to my .lisp file in another buffer and I type:

  (defun foo (symbol)
    (format t "Baz ~A~%" symbol))
  ;; this will print "Baz BAR" in the example
With the cursor anywhere within that function definition I type C-c C-c, go to the debugger and restart it. It works now.

My source file and my Lisp image are both synchronized.

In the case of changing a variable. Say I'm somewhere deep in some code and this tries to run:

  ;; in context x = 0
  (/ y x)
I'm dropped into the debugger. I know x was supposed to have a minimum of 1. I can do a couple things:

1. Examine the back trace in the debugger to see if I can locate where x got an incorrect value (maybe it was the line above, maybe 10 functions earlier). I make a note of what to fix, maybe I fix it now.

2. Change x to its proper minimum value and resume.

If I was able to trace how x got a wrong value, it's fixed already. If not, I'll have to explore more. But I don't have to restart way earlier, I can just restart at this point (assuming nothing else was broken by x having the wrong value here) and still get the result of my program (assuming x being wrong didn't break a lot of other things). Or I can quit back to the REPL and start exploring the problem.


It depends what kind of environment you're using. Eg, notebooks like Jupyter are a kind of REPL environment, which is how data scientists typically write code. You prototype in a cell that is the source code that gets saved. If you've finalized the function or piece you're working on, you go to another cell.

The reason data scientists do it this way is load times are gruesome for data science work. Sometimes it takes days to process something. Imagine every time you change a line of code having to wait days to see if there is a bug in code or what the output is. Instead we run it in an environment that loads it once and keeps it in ram (a REPL), so when working on code below what is already loaded there are no load times.

There are other kinds of environments though. Sometimes video game devs develop on a REPL where the game is running, they pause it, update some code, and then go back into the game with the updates automatically put in. No load times.


> At the end of the day, you quit your environment and shut down. How do you ensure your interactive work is not lost and the environment is still what you expect it to be when you start again the next day. How would you compile such a program?

Modern Smalltalks have solved this problem. Smalltalk has the concept of Packages just like Java, and as you go along building your environment, even though you are modifying the Smalltalk image, you can export these packages to plain-text files, and put them in Git, just like any other language. The environment itself supports Git integration (called Iceberg in Pharo).

> Also, if significant parts of the source code are written inside the REPL, wouldn't the lack of modern IDE features be a hassle? No syntax highlighting, no code completion, no code inspections etc. Or are there tools that offer those?

The command-line REPLs that other languages have are NOT what you get in Smalltalk. I believe the author means the entire interactive environment, and the "style" of development is REPL, not the actual UI. The Smalltalk "IDE" is just as powerful as any other IDE, including code completion, automatic generation of certain getters/setters, renaming methods/classes, finding uses, jumping to declarations and even refactoring within methods. The difference between a normal IDE for Java is that this "IDE" is pervasively available, including in breakloops and the debugger. Since the system is live, there is no separate notion of debugging, the debugger is always there, and you can use all the editor IDE features when stopped in a debugger. You no longer have to deal with a crippled debugging environment way different from your authoring environment. It truly is mind-blowing!

I highly recommend giving Pharo Smalltalk a spin (by following their MOOC or similar). This video is also worth a watch - https://www.youtube.com/watch/baxtyeFVn3w

I did most of this year's Advent Of Code in Smalltalk and saved it in Git just like any other language. Someone else can then import it into their image. https://github.com/nikhilm/AdventOfCode2020.

Note that the source code looks very verbose, but you never actually interact with the source like that. The source is just a serialization. Your actual environment only ever shows you UI elements and entire IDE windows describing your classes and individual methods.

The only thing I miss in Pharo is that it doesn't have Vim keybindings :) Apart from that there is a significant lack of OS integration and polish, but these are due to the small community and priorities than fundamental deficiencies.


Thanks for contributing from a Smalltalk perspective.

Yes, you're right: as I've repeated several times, when I say "repl-driven programming", I am not talking about a repl window or a command-line shell. I'm talking about the actual read-eval-print loop, the runtime feature that reads input, evaluates it, and presents results. I'm talking about a language and runtime that is designed to be modified while it runs by evaluating incremental changes offered interactively by the user.

It's not about a prompt in a terminal. It's about a software-construction system that is designed to be modified interactively as it runs. Smalltalk is quintessentially that.

It seems like some folks have encountered a repl UI with a prompt in a terminal and jumped to the conclusion that this specific kind of UI is the repl. It isn't. It's one particular kind of UI for a repl. There are others, and there have been others since forever. For heaven's sake, Smalltalk 76 had other kinds of UI for its repl in, well, 1976.


What a surprise, the functional folks are arguing that

"Because your language is different than mine, therefore it's not as good as mine."

I've heard people say that Java or C++ isn't a language, because it doesn't have features x-y-z when Racket does. Garbage. Similarly, this guy is saying that because Python's shell doesn't have breakloops, it isn't a real REPL. Also garbage. 99.9% of the time when using REPLs/shells, if something doesn't run, I correct the code, copy and paste it into the shell, and run it again. Breakloops don't exist in the Python shell because they don't need to. Python shell works about as good as Clojure REPL for me.

I get really tired of these elitist holier-than-thou arguments. Functional programming is a thing. Procedural programming is a thing. REPL is a historical name... for an interactive language shell. They really are quite equivalent.


You misunderstand me.

I am not saying that your language or your way of working is not as good as mine. I'm not saying that you or anyone else should discard your ways of working and adopt mine.

I'm not saying that Python's repl isn't actually a repl. I'm not saying that everything needs to have breakloops. I'm not saying that I or anything I like is any holier than anyone else.

What I'm saying is I like a specific style of programming and it makes me happy. I think more people should know about it because it will make some of them happy, too. I would like that.

Besides just liking to make people happy because that's the kind of personality I have, I would also like it if demand for tools that support my preferred way of working increased, so that there are more of them available over time rather than fewer.

And, finally, I like to use the phrase "repl-driven programming" for the style I like because some time ago someone asked about what distinguished old-fashioned Lisp and Smalltalk environments from others that the questioner was more familiar with, and "repl-driven programming" was a phrase that person used. So I adopted the terminology. I think it's a reasonably good piece of jargon for its purpose.

We can sensibly distinguish a repl-driven environment--that is, an environment whose design is driven by the requirements of interactive programming with a read-eval-print loop--from an environment that merely has a repl as one of its affordances.

If the distinction isn't meaningful to you, then presumably "repl-driven" design isn't either, and you're presumably not one of the people I'm talking to.


I don‘t like REPL for programming but find Jupyter or Org-Mode‘s Literate Programming options is super useful.

Not Ruby's default REPL, but Pry Stack Explorer provides this functionality to Ruby

Legal | privacy