(READ, EVAL, and PRINT are all old Lisp primitives, and the REPL was literally implemented with them. There's also a complex macro called LOOP but it was added much later, and is not integral to the language)
I normally only use a repl when I don't know what I'm doing.
My normal process with a repl in an immutable language goes like this:
1. Assign something to a name
2. Realise that was wrong, try again.
3. Get told no. Remember to tell the repl to forget the wrong one and do it again (or choose a new name).
4. Try to assign something else to another name (probably 'foo')
5. Realise that I already used foo about an hour ago in a different experiment, and hadn't closed the repl in between.
Immutable is great for actual programming. Less so for quick experiments.
Immutability is not the same as rebinding a name (in clojure at least) e.g. the following is perfectly valid (global and local binding of the same name repeatedly)
(def a 1)
(def a 2)
(def a 3)
(println a)
=> 3
(let [a 4
a 5
a 6]
(println a))
=> 6Racket does have immutable data structures and Dr. Racket has a good REPL IMHO.
I quite like immutable datastructures, as it happens.
Actually I prefer unit tests over a REPL the same reason I prefer bash scripts over one liners or SQL scripts instead of typing into the interpreter... I don't like the ephemeral nature of REPLs.
Also with true REPLs unlike the debug unit test approach I mention with Java you really need the language to be dynamic. I'm not entirely sure why but static type languages are not very good at allowing code modification while running (I mean I have ideas but I don't know precisely if there is an actual theoretical limitation).
I guess I prefer static analysis over the complete ability to modify the code base while running.
Only add my 2 cents because the article doesn't mention any negatives to REPLs.
Instead, you develop code in a file, but constantly evaluate code as you go along.
When I work on a clojure project, I very rarely open the actual REPL, but I am constantly evaluating code and experimenting with different implementations of functions.
Then, when I'm happy with the results, I ask the editor to evaluate and insert the results back into the editor. This then becomes the unit test.
> Instead, you develop code in a file, but constantly evaluate code as you go along.
Yes but what you are describing is hot code replacement and evaluation. You do not need a REPL. For a concrete example Java + JRebel + Debugger (Eclipse calls it Display with a glasses icon) will do that for you.
In my mind a REPL is very much about the input and of course the output otherwise its basically what I mentioned above.
And I think the the article doesn't really go into any new innovation or attempts at making REPLs better (particularly because they mention Bret Victor)... ie better input and better output.
Yes advanced REPLs have history saving capabilities and what not but then they are basically competing with the rest of the editor, IDE and source control.
Really innovative REPLs I think are what Bret shows, as well Squeak, and Racket. Those environments offer really unique input and output.
Half the time I find I go the other way. I develop in jupiter, and copy the function to a file once I get it working.
SLIME provides slime-repl-save-history ...
There are/were true REPLs with static languages, Mesa/Cedar and Oberon are two examples that come to mind.
On Mesa/Cedar's paper they refer that they wanted to provide the same experience as their Smalltalk and Interlisp-D environments.
On Oberon's case, it required just recompiling/reloading a specific module, which given Oberon's compile speed, was pretty quick.
.NET has now a REPL, which coupled with Edit-and-Continue (when it works) is also quite good.
I used to use Jython/Groovy as my Java REPL, now just have to wait for the Java 9 release.
You'll have to patiently elaborate more for me. Do you mean because the editors keep track of it and that you are working with immutable/idempotent stuff? Otherwise IMO it is ephemeral because you are mutating things and you can forget what you have loaded and what not. I'm probably wrong though.
> There are/were true REPLs with static languages, Mesa/Cedar and Oberon are two examples that come to mind.
Yes many do including my favorite of OCaml's utop but not many allow hot code replacement for a currently running program. I think the author alluded to that. Of course I have no experience with Mesa/Cedar Oberon. I'll have to check those out.
> I used to use Jython/Groovy as my Java REPL, now just have to wait for the Java 9 release.
I used Groovy as well but mainly because I didn't want to load a full IDE to test a couple of things. As I mentioned before I think with Eclipse/IntelliJ + JRebel + Debug attachment you can get damn close to a REPL.
And depending on how you define REPL I think hot code replacement + debugger might actually be more powerful than a REPL but I have to explore that thought some more.
So I actually created a java REPL: http://jpad.io
I would say the things it does that the typical junit run does not are:
1. Explorative queries, send sql statements see the results quickly. What particularly helps here is that any collection is converted to a table with each column representing a getXXX method.
2. Command line instant queries. Sometimes I just want an advanced calculator on the command line. I do "jpad -e 2+2" and it returns 4. No messing around with IDEs.
3. Automatic smart guessing of imports and ability to upload results straight to website to share with colleagues.
That's what I liked, so I built it in. The lack of traffic may suggest others did not find it as useful :)
Sort of in a round about way but in my original comment my hope was to elicit the discussion that modern development tools of ide visualization + debugger + hot code swapping are not far off from traditional REPLs and in same cases better because the inputs and outputs are better.
That is traditional REPLs (ie commandline with maybe some readline capabilities) I think aren't that much better the inputs/outputs aren't that good.
To your point on the static analysis I agree but a truly interactive development system that allows google-esque querying I think is far more than a REPL or at least the REPLs I know/knew of but I guess REPL definition can be somewhat nebulous these days.
> 1. Having too many unit tests makes your codebase harder to evolve. You ideally want to have as few tests as possible capture as many properties of your domain as possible.
Yes but I have found what often happened for me with with REPL environments is the actual code base would be littered with stuff to massage the REPL (commented out or left behind). At least with the unit tests that playing around stuff is away from the actual code.
For both cases there is always the delete button :) . Also for some reason many developers I have worked with don't seem to have a problem deleting or putting an ignore on a test. After all the tests are source controlled. I do get your point but I don't think its that strong.
> 2. Tests can only ever answer close-ended questions: "does this work?", but not "how does this work?", "what does this look like?" etc.
I fail to understand this point. I mean you can obviously write tests that just run stuff and not throw an exception or error. Furthermore you can share how you set stuff up with other developers.... and again you can just delete it if its obnoxious.
> 3. Tests typically won't run in real-world conditions: they'll use simple, artificial data and mocks of services such as databases or API clients. As a result, they don't typically help you understand a problem that only happens on real-life data, nor do they give you confidence that the real-life implementations of the services they emulate do work.
This is exactly what I do not like about REPLs. You setup a custom environment and its hard to keep track of what you have done. I don't like the "not repeatable" nature of it. I do think you make excellent points about how immutability helps that problem as stuff basically becomes a log but for other languages this is not the case.
However this is by far your strongest point. There are languages that allow you to play with a system while its running. Perhaps not through the command line but through a debugger. The Java debugger in Eclipse/IntelliJ can evaluate expressions and are not far off from being REPLs.... in some cases the debuggers are stronger than REPLs.
Clojure managed to make the tremendously powerful JVM debugging ecosystem completely useless (without providing any replacement with equal power).
Java is not a fun language to type expressions in and IMO neither is Python albeit for completely different reason. I can elaborate more if you like but I think most will agree.. Java will be a pain to type in a REPL.
This is exactly why I love Python. My Django webapp gets features (DB reports, external API pushes, etc) added as the client's budget allows, and before they are I'll often do them manually. Given that the UI for a new feature is usually the most work, it's been working well.
So the process usually goes: REPL/Django shell -> Django management command -> End-user facing feature. I'll grab what I did the first time in the Django shell, and put it into model logic plus a tentative management command. Then the next time I have to do the task I'll make sure the command works properly. And then when the budget allows I'll add access via the UI.
Ninja edit: I forgot to mention that `import ipdb; ipdb.set_trace()` is invaluable to get to the point in the HTTP response code where you can start adding new stuff or diagnose errors directly.
Hot loading is typically a deployment feature that doesn't interact with the debugger at all (it doesn't require the program to be paused). Fix and continue (from smalltalk) never put the program back into a good state after code update.
I'd really love to see what languages designed specifically with ergonomics/tooling in mind look like
Warren Teitelman (and later many people at Xerox PARC) did this with BBN Lisp/Interlisp, starting with real paper teletypes through to graphical workstations.
[1]: http://jupyter-notebook.readthedocs.io/en/latest/examples/No...
https://en.wikipedia.org/wiki/Wolfram_Mathematica#The_Notebo...
The ease at which one can get any language (or I/O machine) to play along with this workflow in Emacs is astonishing.
Picking them all up is a tall order though.
If I remember correctly, XEmacs used to support it (which was my favorite fork), but it seems to have faded away.
For example, try to achieve this demo on Emacs.
(As noted before, REPL is a Lisp term that stands for:
read - from keyboard input, parse the input string into the syntactic structure of the language
eval - eval the expression, this includes binding variables or defining new functions, also re-defining functions, even if such function is currently under execution on the running program.
print - print the result of the evaluation (in Lisp all expressions evaluate to something, even if this 'something' is NIL).
loop - go to 'read')
Minor nitpick, but note that if you define:
(defun foo () (values))
Then (foo) does not return a value and accordingly, the REPL prints nothing. But in a context where you need a value, that value would be NIL: if A evaluates to 3, then after (setf a (foo)) it will evaluate to NIL.Though Common Lisp adds the nuance of multiple values, the behavior you describe is how it conforms to this general expectation. Code written in an everything-really-has-one-value dialect of Lisp can be easily transported to Common Lisp (or at least transported without without difficulties specifically caused by this issue).
Scheme, a Lisp-like language, allows some evaluable expressions to have an "undefined" or "unspecified" result value. Logic translated to Scheme from a Lisp dialect without attention to this issue can have a surprising or incorrect behavior. For instance, if the original code executes a do loop, with the expectation that it yields nil (or some similar false/empty value in the original dialect). In Scheme's do loop, if the result expression is present then it specifies the value; otherwise the value is not specified.
The SBCL and CCL REPLs do not support readline-style editing. This actually makes them infuriating to use outside of Emacs or some other IDE-like environment. There is definitely a market for a high-quality, implementation-independent Lisp REPL.
TL;DR: I fail to see how this can be a problem.
Yes, but they do work fine inside Emacs (or perhaps inside other IDEs), and if i was using the SBCL command-line and needed readline-style-editing, then it was because I'm developing or debugging, so I'd be inside Emacs (or other IDE) in the first place...
This is not a dig at Kotlin/Java/Whoever but it bugs me (coming from Ruby) no end when regexen don't get to have a regex literal syntax and a match operator. I was going through the Kotlin language docs last night and its such a concise language with well thought out syntax and this omission jumped out at me.
Is it me? Do others really not think it's a big deal? I learned how to code via BASIC then ASM then C and early C++ and none of these had regex literals so for the longest time I literally (hah) did not know what I was missing. Now I can't imagine why a language wouldn't have them. I guess Ruby shows its Perl heritage. But Javascript has 'em, you go Javascript, and thus Typescript. And don't get me started on raw strings """Yuck!""" dear Lord, how gruesome. I think how Perl6 is brace savvy is the way forward. I'd also like to be able to specify my own braces to construct my own type as a shorthand, that'd be great DSL, so that,
i = %something%
would construct an instance of type Foo assuming the correct %T% (by way of example) constructor syntax. That'd be neat-o.I do however find REPL to be invaluable when experimenting/doing research. When you don't even know what the end result is, or when exploring data, you need to iterate over many ideas as quickly as possible and REPL is the fastest way to do that.
Hydrogen is quite nice for python repl development in atom. Hydrogen connected to a remote kernel plus a script to synchronize files to a remote server replaces writing code in Jupyter notebooks for me (I just can't enjoy editing code in a browser ...)
[1] -- (https://github.com/millejoh/emacs-ipython-notebook/blob/mast...)
Code with a lot of mockable dependencies is usually considered testable but sometimes it's a pain to setup.
Accessible code seems to fix this. Instead of taking dependencies, return some data so it's easy to check what your component does in isolation.
In practice the compiled languages I've used extensively (OCaml and Haskell) do have REPLs, but ones that aren't nearly as powerful as some other languages. I'm not sure exactly why it's the case, but I certainly don't think it's impossible for them to have good REPLs. My guess is that there are some properties of the languages that make a good REPL a bit more difficult to implement, and there simply hasn't been enough community investment to overcome that.
I wish there was because I basically live in GHCi (Haskell's REPL) and sorely wish for a few core improvements like hot loading updated code when possible.
CMUCL does that. There's an interpreter that's used for the REPL and optionally for loading files on the fly, and an optimizing AOT compiler.
SBCL drops the interpreter and just runs the compiler with settings that make it reasonably fast for interactive use as I recall. Clojure, too just uses the compiler interactively and not a separate interpreter.
1) You can define new functions (and values, and types, type classes, instances, etc). You can "redefine" these things only insofar as you can shadow them.
2) I'm not sure whether they mean the ability to persist your state to disk and restore it (which GHCi lacks), the ability to refer to previous results (in GHCi, the previous result is called `it`), or just the ability to bind variables (of course you can do this in GHCi).
3) Usually "Show" instances are meant to be embeddable in code. Sometimes they need a little massaging. Sometimes they're just broken, from this POV. Sometimes they're just broken, period. But it holds for a lot of values.
4) You can run GHCi in the context of your project (see cabal repl and stack repl commands).
5) GHCi very much fails at this - no way to add anything to a module, so far as I'm aware.
6) GHCi more-or-less lacks this kind of functionality. You could run your server's "main" function from the REPL, but there's not much you can do to it.
7) :reload
8) There's an increasing amount of such tooling; only some of it has any particular tie to the REPL, per se.
And it seems nobody ever tries hooking a REPL up to a running system anymore.
Lots of Common Lisp systems have very good REPLs and compile to machine code. A popular example is SBCL.
It's great to be able to see the entire run of your current and past repl sessions.
But... that image saving feature is very much like that of Common Lisp. Given Ross Ihaka's then-and-now fondness for CL, I'd be shocked if this feature weren't very much intentionally patterned off that. The original implementation of R was on top of a Scheme runtime, but I don't know if images were (then) a feature.
Common Lisp and Scheme do not have any specifications for persistent state, and the implementations that do have images are all over the place in what those images do and how they are made.