Room 101


Gilad Bracha's blog. A place to be (re)educated in Newspeak

First version December 16th, 2022; revised and posted March 11th, 2024

Replacing the REPL 

Time to Terminate the Tyranny of the Teletype

It is sad to see that many programming languages implementations limit their interactivity to REPLs. It is even sadder that their users accept this strange state of affairs. To make matters sadder still, many REPLs represent regressions from the early designs. Let's review.

Suppose you have a function squared: .

 
squared: x (* :exemplar: squared: 3*) = (
  ^x*2
)

There's a deliberate error in squared:.  Rather than using the exponentiation operator, **, we used multiplication (*). Now we use squared: in a new function, cubed:

 
cubed: x = (
  ^x* (squared: x)
)

Below is the  result of evaluating cubed: 3

    

The result is wrong; we need to fix squared:. To do that, edit the squared: method to use ** instead of *, and click the green accept button in the method's upper right corner. You'll see the result change to the correct value. This is what you should expect.

If you undertake the analogous effort in the REPL of a typical functional language like Standard ML, OCaml or even F#, you'll still get the wrong result (though type-correct, of course). Here's OCaml in action:

# let squared = fun x -> x*2;;

val squared : int -> int = <fun>

# let cubed = fun x -> x * squared x;;

val cubed : int -> int = <fun>

# cubed 3;;

- : int = 18

# squared 3;;

- : int = 6

# let squared = fun x -> x*x;;

val squared : int -> int = <fun>

# squared 3;;

- : int = 9

# cubed 3;;

- : int = 18

# 


ML and its relatives don't support redefining functions. Instead, the REPL creates the new definition of squared() as a nested let expression. This is perfectly logical if you view the REPL as simply incrementally constructing a program. It just isn't useful if you want to use a REPL as a development tool.  These REPLs can be used as a tool for education, where you can see what constructs do, but not for real development. To do that, you revert to editing files, feeding them to the batch compiler and rerunning your code. What James Gosling and Henry McGilton called the
edit-compile-link-load-throw-the-application-off-the-cliff-let-it-crash-and-start-all-over-again style[1].

What about a dynamic language, like, say Lua? Lua will handle the case above as you'd expect, but it has other quirks. Lua's REPL simply won't evaluate an expression. It demands that you call print() to see the result.

> 3 + 4;

stdin:1: unexpected symbol near '3'


The idea here is that the REPL processes well-formed statements, but Lua doesn't have expression statements, so it won't accept expressions. Again, very logical, in a Vulcan sort of way.  Rather tiresome to use however.  The lesson here is that relying exclusively on the fixed language semantics for your REPL is a mistake. It makes it easy to build a REPL on top of your existing compiler, but that REPL won't be a good interactive development tool.

Let's try another dynamic language - Python. The Python REPL will do the right thing here - but it's actually quite inconvenient to define functions or classes that are much larger than our trivial example. For more substantial development, you'll go back to files, which you can edit and then quickly import into the REPL to experiment with.

Part of the problem is that the REPL was designed around line-by-line interaction. That model was dictated by the hardware available to interact with computers - the venerable teletype. My first experience in programming was with BASIC, using a machine like this:



Teletype image by Marcel Whichary under the Creative Commons 2.0 Generic License.

You type your code on paper, and press line-feed & return and the computer responds by literally feeding in the paper to move to the next line, and returning the the print head (aka the carriage) to the start of the line and typing back at you. 

This is why there are separate ASCII codes for LF (line feed) and CR (carriage return). Think of all the pain this has caused, with incompatible treatment of newlines in various systems etc.

You can't go back and edit what you typed earlier - it's been put on paper and that's the end of it.

And yet, back in the mid 1960s, the BASIC REPL was able to support development under the harsh constraints imposed by the hardware of the day. Tomas Petricek has a 
lovely piece exploring how that works. And the old APL REPLs did much better still. These systems managed a program as a set of lines and allowed you to address those explicitly, mitigating the single-line tyranny of the teletype. 

Once you had terminals with screens, the single-line limitation wasn't really there anymore - but the model of interaction typically stayed the same. Your Unix CLI is only marginally better today. The very term Command Line Interface tells you it dates to the time of the teletypes.

Another place you can still find traces of those limitations is in the ed editor on Unix. It's a leftover of editing's reptilian brain -  it was called line editing as opposed to full-screen editing. You don't want to use ed to edit anymore - why do you want to stick to that model to evaluate code?

If you enjoy seeing what ossified traces of antiquity live on in our supposedly modern machines, check out Jack Rusher's talk. Take a look at the related website to see many cool designs ignored by the mainstream.

You can do a lot better if you run your REPL under an editor that will let you access what you typed before, modify it, and easily resubmit. I used to do that a lot with emacs in the early 1980s. Lisp environments like Cider or Slime follow that approach to an extreme, integrating the REPL into a suite of text-based tools using emacs. Just as the early REPLs were actually a very clever design for interacting via teletype, emacs is a brilliant design for working with text-based terminals.

I've written about the charm of emacs elsewhere, in a post that anticipates Ampleforth.

The things is, we have better hardware than VT100s. At roughly the same time that emacs was developed at MIT, the folks at Xerox PARC were working with bitmapped displays and mice, and developed ingenious tools to leverage the new hardware.

One of the wonderful results of their work is the Smalltalk workspace which provides a two-dimensional editable space in which you can evaluate code snippets. The workspace does away with the idea of resubmitting your code on the command line and getting your answer there. It also does away with the idea that the only response you can get is plain text. You can evaluate expressions and immediately get an object inspector on the result. An inspector is an interactive UI component, which also contains its own evaluator, much like a workspace but scoped to the object you're inspecting. Now things are compositional - you interact with an inspector, and can get additional inspectors on the results, indefinitely. 

The Lisp systems mentioned above also have a compositional aspect - but they limit themselves to text alone. One of the key aspects of Smalltalk environments is the use of a GUI.

That isn't too surprising, since modern GUIs were invented in Smalltalk. What is surprising is that even though GUIs are ubiquitous in every other domain using computers, programmers stick doggedly to the text based practices of the 1970s.

Here's a Newspeak workspace, which is an evolution of the Smalltalk workspace.

   
    

When you evaluate something, you get a link to it below the evaluator. That way, a single interaction subsumes the evaluation, printing and inspection functions of the the traditional Smalltalk workspace.  

In fact, a Newspeak workspace is just an object presenter on a Workspace instance.

The next step is fusing workspaces and rich hypermedia document editors like Ampleforth, the editor used to create this post, to combine the strengths of both text and GUI.  Below is a simple example.
   

A Sample Ampleforth Editor

  Instructions: 
   
  1. Open the toggle directly above this nested editor to reveal the toolbar.
  2.  Select one of the expressions below.
  3.  Click the Show It button from the toolbar; the expression will be evaluated in place
        3 + 4
        
        {1. 2. 3. 4} size

        42 max: 91

  Or watch this video to learn more:
 



 Of course, Ampleforth can do a lot more; there are options to evaluate GUI widgets and embed them in the document, for example, which is how this post was created.  You can also define (and re-define) classes and methods. I'm still experimenting with the UI options for this sort of thing.

We should also prepare for the next steps in hardware, such as VR goggles. Imagine specifying 3D objects programmatically at some point in space around you, and having the resulting object materialize. That discussion will wait for another time.

Bottom line: two generations ago, the REPL was invented. It was a brilliant idea, well adapted to the hardware of its time. Since then, it has been replicated many times, often badly - but like the teletype, its time has passed. REPLs should have been replaced long ago. 

[1]Page 52 of  The Java Language Environment :A White PaperJames Gosling and Henry McGilton