I am not convinced that immutability matters; this seems like a bias. After all the original REPL, and the name itself, was in Lisp (note that the first Lisp implementations were not interactive, but it was the first interactive language) and Lisp doesn't have immutable data structures.
(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)
Author here. I could be wrong of course, but this stems from my experience in using a REPL from JavaScript. I hit 2 sorts of hurdle with mutability there: 1- I wasn't sure what I was viewing in the REPL was what the code of interest was perceiving, could have been changed afterwards and 2- immutable values were more suited for exploratory work due to their speculative nature, e.g I could try a function calls with variants of a data structure without 'committing' to them. Maybe I had a bad REPL experience in JS and blamed it on immutability; OTOH, maybe other people had a great REPL experience in Common Lips for other reasons, and attribute it to mutability. This certainly makes me want to give a new try to Common Lisp :)
I think there may be some confusion here, immutability is about the stability of values (e.g. I have a string, list, map etc and I want to pass it to others, view its value, make changes without affecting the original value).
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))
=> 6
I see. When I see "immutable", I think of Prolog and Erlang (e.g. once X is 6, X can't be matched to 5 in the same scope) rather than languages that simply prefer byval over byref.
If you can shadow previous bindings then that would also cause no problem for repl re-usage, unless the shadowing syntax is different to the binding syntax.
Either way, unrelated to the point in the article about the advantage of immutable values for repl, not immutable bindings.
I would certainly agree that truly immutable bindings would be at best quite a stumbling block for repls, but so far no one was advocating for them as a good repl feature.
For a global def to be a "rebinding" rather than assignment is crippling. It means that previous references to a still see the old thing, which is wrong if you want them to see the new thing.
If you want others to see the new thing automatically, you need something much more powerful than plain old assignment anyway: something which will let you pick a synchronization strategy.
"Changes at any arbitrary time, including when you are halfway through reading it" is not in any way a sound synchronization strategy and the only thing you get with assignment.
You seem to understand the important difference between identity (what you see when you read an object) and reference (what you use to access the object). Next important thing on the list is how "reference" cannot just be a pointer to a place in memory -- unless it is immutable.
Clojure does not rebind the reference, it just changes it to point to something else. So it does not suffer from what you describe. Previous references will see the new thing as you'd hope.
So def on an existing binding does assignment. That's what is meant by "re-binding".
Indeed, by immutability I really meant immutability of the values, but not of the name bindings. So I would say an optimal setup is to have a mutable execution environment which handles immutable values.
That's the converse of my point. The article claimed immutable data is important for a good REPL and I said that the claim was an overstatement. I didn't say that immutable data makes a REPL impossible.
I quite like immutable datastructures, as it happens.
Common Lisp has some immutable data structure such as, oh, numbers. If you arithmetically encode a record of data into a bignum integer, that is immutable.
If you write a custom _repr_html_, you are likely to assemble subparts by recursively calling _repr_html_ on sub-elements (thanks to open recursion).
If you have an object B which inherits from A, you can specialize on B, or use the existing method for A.
However, you cannot specializes the "html" part with inheritance.
Say for example that you want to define a slightly different _repr_html_ method depending on the context (maybe you target the subset of HTML that works correctly in emails) or if you want to render "latex" with custom "tikz" macros instead of using matplotlib.
With multiple dispatch, you can specialize on the target too, which means you can specialize wherever required or fall back to a generic behavior if not.
Using Emacs' Elisp for a short while helped me unlearn some REPL antipatterns I acquired during Clojure development (some of them unfortunately presented in this article), especially this: instead of typing something in the REPL, executing it, then copying it back into your actual source file, you can just write it in your source file and use "evaluate current form" or "evaluate form before current point" to send it to the REPL process for evaluation instead of painstakingly entering it in the REPL and copying it back. Note that this is not a conventional hot-reload, but the form is sent as literal to the REPL (where indirectly, hot-reload may occur).
With fast compiling languages a unit test is not for off from being a REPL. In Java I use JUnit as a REPL.
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.
The thing to realise is that, at least is my experience, REPL based development doesn't mean your are actually typing code into the REPL.
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.
> The thing to realise is that, at least is my experience, REPL based development doesn't mean your are actually typing code into the REPL.
> 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.
One nice thing about the Python REPL IPython is the ability to turn the ephemeral session into a file which contains every command in the session like a history file by just typing %logstart. Most of my projects start this way.
> REPLs are not ephemeral on Common Lisp environments.
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.
Yes I agree on the Smalltalk point. I think Squeak was at one point the very future of REPL-like development and hence why I think the article doesn't really go into how REPLs could be better and how they aren't really that much better than other development tools (ie IDE + debugger,... I mention this in other comments).
I had no idea that common lisp had image saving! I only used the Carnegie Mellon one in college.... its been a long time.
I probably did at one point know this as I did use it in college but for some reason forgot it (we are talking 15 plus years) given that is apparently how you distribute executable lisp code... IIRC though I hated image saving when it came to Squeak aka Smalltalk so I'm not sure if I did like that.
I also used junit as a semi-REPL similar to how you describe. then I started playing with a REPL for a fully dynaimic language and when I came back to java, there were parts that I missed.
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 :)
Yes I think you make an excellent point but more so because the output of the REPL session is much better than a typical REPL session of just plain text.
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.
The interactive development workflow in Idris is best described as type driven development and the focus seems to be on making the REPL an interface where an intelligent dialog can happen in between the programmer and the compiler, with the compiler trying to nudge you in the right direction. It is kinda fun, you should definitely check it out[1].
And regarding your point about the modern IDE workflow, I personally think that the sweet spot is somewhere in between, I personally prefer having a REPL process in the background I can interact with while still writing code in my source file and a REPL kept open as a scratch space where I can experiment freely without messing up my source code, I feel much better experimenting in a REPL, but to each his own I suppose :)
Jupyter Notebook is a completely different beast though, modern REPL + literate programming, what's not to love.
Definitely start with the book I mentioned, it is by Edwin Brady, he leads the development of Idris. I am reading it right now and it is an enjoyable read :)
I assume you are the author? I think you make valid case/points but I have some kind counter points if you don't mind
> 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.
>> 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.
Yes, but the point is that a test mostly gives you a Yes/No answer, not a visualization. What's more, sometimes you need to set up a fair amount of state as you explore (think of the examples in the video, where you call an external API based on a previous result of that external API etc.) - not something that is convenient to do in a test.
> > 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.
It's not that the environment is custom, is that it's real. Do you call your payment service or your mail sending service from your tests? This is exactly the kind of thing you want to experiment with in a supervised, non-repeatable way.
You can put stuff in the REPL, or in your test suite, or both, and there are pitfalls in each case, but at least you have a choice; indeed, you have to use your best judgement to decide what will be persisted, repeated and shared with your team and what will be forgotten after your REPL session end, but I wouldn't call making the wrong choice a deficiency of the REPL, rather an error on the programmer's side.
> It's not that the environment is custom, is that it's real. Do you call your payment service or your mail sending service from your tests? This is exactly the kind of thing you want to experiment with in a supervised, non-repeatable way.
The irony you mentioning that is I have actually written "tests" that call braintree and stripe...
Just to clarify when I say "test" I don't mean some precise definition of "unit test" or "integration test". I mean in a runner that runs your code. Unit tests basically allow you to make a whole bunch of entry points (aka static main(args)).
I have many times setup a particularly environment and then repeatedly ran a unit test against that environment (I then commented or deleted that code later).
I think in large part of what your saying is good about REPLs is that they allow hot code swapping but that is only IMO really one part of the REPL. The key to really good REPL should be human input (think Excel) and human output (think images and graphs). There was a company recently shown on HN called Luna [1] and I think that is what a REPL should be.
And I particularly pick on this because you mention Bret Victor who is (err I guess was) actually working on environments like this.
Otherwise call it a unit test... call it a REPL... call it hot code swapping but in current JVM environments the difference can be pretty nebulous.
> I think in large part of what your saying is good about REPLs is that they allow hot code swapping but that is only IMO really one part of the REPL. The key to really good REPL should be human input (think Excel) and human output (think images and graphs).
I definitely could have spent more time on this part in the article. You may want to have a look at Proto-REPL (https://atom.io/packages/proto-repl), which is one of Clojure's REPLs (for the Atom editor)
I'm interested to see how useful Java's REPL is going to be. Like you I will use a JUnit test, or maybe a standalone class with a main method, to test a small bit of code. With Eclipse's debug support I don't feel the need for a REPL.
As a person who has used the standard java debugger in intellij for clojure, could you expand on what features are missing?
I find I don't get much use out of it anyway, due to immutable values and (mostly) pure functions I find I don't usually need the whole program to be running and frozen in time.
However to the author's point I believe some languages are inherently superior for REPLs particularly if they have powerful literal syntax.
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.
I agree with your point that IDEs are getting better but REPLs are getting better too, why should they be relegated to be mere artifacts of the past. Your java REPL will mostly have autocomplete, give it a chance, it really might turnout to be fun :P
And typing python in a REPL is fun for me, but fun is subjective, no point arguing about it :)
I'm not against REPLs I just think they have been around since early Lisp and there are vast improvements that can be made.
I use REPLs all the time... Bash is basically a REPL :)
The reason I think Java REPL would be awful is not because I dislike REPLs but because Java is really painful for that kind of mutable command line like development. Like just making a damn struct like object is absolutely painful and Java does not have structural types (ignoring FunctionalInterfaces which isn't really structure types).
Python REPL is painful because of required indentation and again because Python is similar to Java and prefers nominal types.
I'm not against Python or Java but I don't think the language design of those languages really works well for REPL compared to say Lisp, OCaml, Haskell, or even Scala and Smalltalk.
Fair enough, I think I am having difficulty in understanding your views because firstly I have never worked with Java, closest language I have worked with is C# and I don't particularly dislike its REPL, its nothing to write home about but I don't particularly hate it either.
Secondly the image we two have in mind of a REPL seems to be different. When I am thinking about a REPL the image I have in mind is of Jupyter Notebook and Clojure, Elixir, Idris, Haskell REPLs in Emacs. The image you have in mind seems to be a basic console, so having spent my day working in a Jupyter Notebook I sit here thinking what you mean by Python's significant whitespace being an issue(Haskell and Idris have significant whitespace too). But now I do understand your views :)
P.S. I didn't get your point about python having nominal types, duck typing seems closer to structural typing to me and mypy seems to support both, but maybe I misunderstood you. Thanks for the thought provoking discussion though :)
> When I am thinking about a REPL the image I have in mind is of Jupyter Notebook and Clojure, Elixir, Idris, Haskell REPLs in Emacs
Thats my point is that the REPL case of using Emacs to run and evaluate your code is almost hardly different than letting an IDE run your unit test. With Java this evident because the IDE compiles incrementally and the debugger can hot code swap.
The power of the REPL should not be the evaluation portion but the input or the print otherwise you can just about do any quick evaluation for any language that compiles reasonable fast.
Other than Jupyter Notebook the ones you mention don't really have any amazing output other than pretty print. To the authors point it also helps for the pretty print if the language is homoiconic.
As for input there is even fewer that have Excel like rapid response feedback. See Bret Victor on this. There was a recent
company presented here on HN called Luna [1] who have a very cool REPL. Now that is where I think REPLs should be.
> P.S. I didn't get your point about python having nominal types, duck typing seems closer to structural typing to me and mypy seems to support both, but maybe I misunderstood you. Thanks for the thought provoking discussion though :)
For the most part you need to name functions in Python (lambda support I believe is even on the way out but I can't recall the status). In fact other than I guess tuples you need to name everything in Python.
But to your point structural typing means less in a duck typing environment particularly one with really late dispatch.
> Thats my point is that the REPL case of using Emacs to run and evaluate your code is almost hardly different than letting an IDE run your unit test. With Java this evident because the IDE compiles incrementally and the debugger can hot code swap.
Using a REPL vs. an IDE like you describe is the difference between a conversation and sending somebody a letter with instructions.
This is more visible when we use more dynamic languages/runtimes than Java/JVM. Since the changes one can do and how they need to be done is not very advanced, the usefulness of a REPL is reduced.
I have a feeling given your screen name I'm talking to some who is equally biased as I am to Java :)
> Using a REPL vs. an IDE like you describe is the difference between a conversation and sending somebody a letter with instructions.
Hmmm an IDE is supposed to be a REPL and more. I mean you can go look up the definition from wikipedia.
> This is more visible when we use more dynamic languages/runtimes than Java/JVM. Since the changes one can do and how they need to be done is not very advanced, the usefulness of a REPL is reduced.
Yes I completely agree as I mentioned dynamic languages are far easier to modify at runtime. However for the case with Java it can be done with JRebel and various other tools.
Furthermore going back to the whole conversation vs letter an IDE with a powerful debugger will let you evaluate expressions based on a state that is stuck... ie setting breakpoint (as well of course as investigating current variables and such). This is damn useful for dealing with a multithreaded environment.
By the way make no mistake... I do love Lisp... I just think there are better things than traditional REPLs considering to your other point in another thread this stuff has existed since the 70s.
> I have a feeling given your screen name I'm talking to some who is equally biased as I am to Java :)
The main difference: I have a Lisp Machine at home. :-)
> Hmmm an IDE is supposed to be a REPL and more.
No, a Read Eval Print Loop came from Lisp in the early 60s. It originally means to read a data structure, treat it as code and evaluate it and print the result data structure. READ, EVAL, PRINT are actual functions in Lisp. This stuff executes in a LOOP and is enriched by all kinds of stuff.
An IDE does not need to have a REPL. If it can interact with a running application (for example via a debugger), this might still not be a REPL.
> However for the case with Java it can be done with JRebel and various other tools.
Even JRebel can not do to a running JVM application what some Lisp implementations can do. Not near of that.
> IDE with a powerful debugger will let you evaluate expressions based on a state that is stuck... ie setting breakpoint (as well of course as investigating current variables and such)
This is pretty basic.
> traditional REPLs
Check out Symbolics Dynamic Windows and McCLIM on the Lisp side...
I think I'm in agreement with you. What I meant by the IDE is that "ideally" it should have REPL like offerings if the language can support hot code swapping.
I still don't think its REPL that makes Lisp or clojure magic (when I say magic I mean awesome). Its all the other stuff like macros and homoiconicity (which I see your point plays some part in academic REPL).
> Even JRebel can not do to a running JVM application what some Lisp implementations can do. Not near of that.
Well thats because of the Java compiler and in some parts the language of Java. It has nothing to do with the JVM otherwise Clojure wouldn't work. But I agree JRebel is far cry from the full reloading capabilities of Lisp, Erlang and other dynamic languages.
> IDE with a powerful debugger will let you evaluate expressions based on a state that is stuck... ie setting breakpoint (as well of course as investigating current variables and such)
> This is pretty basic
I agree but its still surprising how many languages do not do this well and I didn't mention that you can execute simple expressions in that mode something other static languages like C will not allow.
Besides.... I can change a function name in Java or Scala and see immediately everywhere in my code base with (e.g. red squiggle lines) how that impacts other code... for static languages that is pretty basic :P
I'm totally envious of your lisp machine (EDIT: in all honesty...I realize that originally sounded sarcastic).
Now we set the social security number of the persons. Wait?
Lisp has updated my objects, since I added a new superclass to their class? All objects now have a changed superclass for their class? They inherit the new slot?
And the print-method gets reassembled for the new inheritance tree and the changed set of methods?
CL-USER 14 > (mapc (lambda (p ssn)
(setf (ssn p) ssn))
persons
'("123-345" "321-455" "443-222"))
(#<PERSON Jan 23 123-345 4140473733> #<PERSON Ralph 43 321-455 4140473933> #<PERSON Joan 21 443-222 4140473D3B>)
As you see the objects have a SSN and the print methods are dynamically combined. For the person it runs the around method, then the primary method of person and then the after method of the mixin. If I'd now change the inheritance tree, then the methods would be recombined according to the inheritance at runtime... I could also dispatch on the second argument...
CLOS supports multi-dispatch over multiple-inheritance with dynamic combinations of applicable methods.
CLOS can do quite a bit more than that...
Java can't do anything like that.
It can't update objects on class changes/inheritance changes/...
It can't combine methods based on the multiple-inheritance class tree.
It can't change the class of objects. It can't reprogram the object system itself. See the CLOS MOP...
BTW AspectJ and JRebel will get you around methods and even inheritance changes but alas Java does not have multimethods or MOP. I mean CLOS is awesome but so is static analysis :)
ABCL implements CLOS classes in Java. It does not use the Java/JVM directly. For example a CLOS class is an instance of some Java class. This instance then has an attribute which has a vector of the CLOS slots. CLOS slots are not Java attributes themselves... The JVM object model is simply not able to provide CLOS features directly.
Last I've looked Jrebel used a funny mechanism. One couldn't just tell the class to add a slot, but one has to have Jrebel installed and given a new class file, it will detect it and then change/load the class...
That's a rather limited mechanism aimed at development... especially since it needs a license to work...
I have to agree about Java, but hundreds of thousands of people seem to be happy using Python REPL, even if just to discover an API. At least it _does_ have a literal syntax for things like maps.
Yeah the Python one was a stretch and I often use the Python REPL as a calculator (I have no real strong idea why but I guess because numpy is what I know).
"Finally, not all programs need be fully automated - sometimes the middle ground between manual and automated is exactly what you want. For instance, a REPL is a great environment to run ad hoc queries to your database, or perform ad hoc data analysis, while leveraging all of the automated code you have already written for your project"
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.
I've dabbed in elixir and clojure for some time now and the ability to gain a REPL into a live system opens many opportunities for live debugging and introspection. It's a great tool when dealing with unknown states and bugs in production.
I follow a similar procedure for an open source community project in Ruby on Rails. There are a fair amount of rare things that need to get done where it's just easier to do them in a reply on the web server. One of the admins of the community actually learnt enough about the rails DSL to write basic queries through the reply and do some of the things I usually would.
I can't live without auto reloading in ipython. It's not perfect, but I work in a space where I need to load a lot of state before I can do anything meaningful. Being able to change to code inside running objects and maintaining all that state in memory is an absolute god send.
Hot loading only implies the former (change code of a running app) and not the latter (automatically rerun application and return to paused point).
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 loved this article. I am a huge proponent of development ergonomics, and clojure(script) are really fantastic in this area. Most languages have a pretty similar standard set of tooling, but the pieces that a language does exceptionally well really stand out in cases like this.
I'd really love to see what languages designed specifically with ergonomics/tooling in mind look like
> 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.
Matlab has a nice sort-of-repl feature which I miss in every other language: you can separate the code in a file into several blocks and then execute the current block (the one with which contained the cursor) with ctrl+enter. With this feature you still have the full text editing capabilities but you also have a flexibility you get from a repl.
Most Lisp modes for Emacs have an eval-sexpr-at-point command which allows you to send the current sexpr to the REPL. This is in SLIME for CL but even the most basic Scheme mode has it as well.
Clojure's Emacs REPL also has this; it's strange that the article does not mention it, but talks about copying back and forth between REPL and suorce, which I consider a antipattern and really ugly workflow.
Emacs can do this with Ruby, Python... Hell, you can get it to eval buffer, region, line with anything that'll accept input and return a useful output.
The ease at which one can get any language (or I/O machine) to play along with this workflow in Emacs is astonishing.
You seem to be using SublimeText, check out SublimeREPL extension[https://github.com/wuub/SublimeREPL/blob/master/README.md]. Once setup you'll be able to evaluate code blocks in a running REPL :) Also, Emacs modes of most languages with REPLs will have this functionality inbuilt.
Matlab's REPL experience is great. It satisfies almost all of the "what makes a good REPL" and "what makes a programming language REPL-friendly" criteria in the article. Having a full featured plotting library available to inspect intermediate values when debugging is amazing.
I have never used a Lisp Machine so forgive my naivety but I saw the demo and you seem to be referring to the ability of displaying images inline in a buffer. Emacs can definitely do that, I have used Emacs IPython Notebook[1] which is a REPL supporting this. I could fire up a jupyter notebook and use pillow[2] to recreate the image manipulation part of the demo.
The other part, which might not be visible on that video is the integration of debugger into the REPL, and the ability to redo a piece of code after breaking into the debugger and fixing it.
So you can do something like, REPL => error (ask to start debugger) => track down and fix error => restart error expression => finish the execution of the original REPL expression with the respective result.
The only debugging experience I find enjoyable in Emacs is debugging Clojure using cider[1] and I think it comes close to what you are describing but I think you might already be aware of that :)
SLIME does let you redefine functions and restart a stack frame (assuming the Lisp implementation supports that). You can even do it without using Emacs/SLIME, although that's not very convenient of course. A silly example with SBCL,
~ $ rlwrap sbcl --noinform
CL-USER> (defun foobar (x y)
(if (evenp x)
(/ x y)
(* x y)))
FOOBAR
CL-USER> (foobar 10 0)
debugger invoked on a DIVISION-BY-ZERO in thread
#<THREAD "main thread" RUNNING {1001BB64C3}>:
arithmetic error DIVISION-BY-ZERO signalled
Operation was /, operands (10 0).
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [ABORT] Exit debugger, returning to top level.
(SB-KERNEL::INTEGER-/-INTEGER 10 0)
0] backtrace 3
Backtrace for: #<SB-THREAD:THREAD "main thread" RUNNING {1001BB64C3}>
0: (SB-KERNEL::INTEGER-/-INTEGER 10 0)
1: (FOOBAR 10 0)
2: (SB-INT:SIMPLE-EVAL-IN-LEXENV (FOOBAR 10 0) #<NULL-LEXENV>)
0] down
(FOOBAR 10 0)
1] source 1
(IF (EVENP X)
(#:***HERE*** (/ X Y))
(* X Y))
1] (defun foobar (x y)
(if (and (evenp x) (not (zerop y)))
(/ x y)
(* x y)))
WARNING: redefining COMMON-LISP-USER::FOOBAR in DEFUN
FOOBAR
1] restart-frame
0
A "steep learning curve" actually means that the amount learnt grows rapidly over time. It's odd how this basically obvious conceptualization has had its meaning inverted in popular usage.
This is one of my pet peeves, as well. Though I have moved on. Colloquially, it is clear how folks take that saying. Just transpose the chart if it helps you think about it.
That is, it may have originated with that meaning. But it is far from unique in having the general meaning shifted with use.
Take a look at Smalltalk's environment and take a look a t Common Lisp's REPL. They have all the features that make for a good 'REPL'.
(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).
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.
According to a famous epigram by Alan Perlis, Lisp programmers know "the value of everything, but the cost of nothing". It reflects the expectation that a language which calls itself Lisp is expected to produce a value out of any expression which evaluates.
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.
> The SBCL and CCL REPLs do not support readline-style editing. This actually makes them infuriating to use outside of Emacs
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...
Also linedit. But using it still requires you to add it to your favorite implementation's init file. Would it be possible to write a "meta-REPL" using linedit? Then again, there already is a Jupyter kernel for CL, so maybe I should look into making that work for me.
> Data literals. That is, the values manipulated in the programs have a textual representation which is both readable for humans and executable as code.
--8<--
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 personally prefer to develop by writing small functions and pairing them with a small test. Hacking in a REPL environment, saving the result and calling it source code has not worked out well for me, I end up producing spaghetti :)
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.
Quokka adds a nice repl like experience for JavaScript/typescript to various editors. I'm a fan of their products!
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 ...)
Hydrogen seems nice, I also occasionally get tired of the default jupyter notebook interface and use Emacs[1] to edit my notebooks, this way I can have all the niceties of jupyter notebooks without working in the browser :D
There's a lot of excitement about compiled languages lately, and many seem to wonder if interpreted languages are dying. Unfortunately I don't see the value of a good REPL brought up in those conversations very often.
I don't think there's anything fundamental about a compiled language that prevents it from having a REPL. (If anything, you could always have a dedicated interpreter for interactive work—"compiled" and "interpreted" are functions of the implementation, no the language.)
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.
> If anything, you could always have a dedicated interpreter for interactive work
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.
I wonder how do that. I'm building a language that truly will benefit for a REPL (is for database development) but also do it compiled simplified other things.
Is done on .NET/F#. I wonder how architect the thing so I can have a good repl yet compiled... but how?
I think the compiled language analog of a REPL is something like LINQPad for C# where you code in the top pane, highlight a snippet to execute, and get the results in the bottom pane. Combined with the more functional-style language features it's very conducive to layering on complexity and analyzing code where you can execute inner layers to see what they do.
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.
Yes for (3)! I just plainly hate data types whose Show instances aren't just Haskell syntax. They usually try to add some pretty printing but that just ends such hurting copy-pasting from the REPL to the editor.
I'm curious what makes this superior to Beanshell. Or any other style of REPL that you could do on the JVM.
Not against the idea, per se, but it seems hardly new ground. And unlikely to be nearly as powerful as a REPL in CL. (Though, again, few things are. Not sure that any are, to be honest.)
My question in that vein is more of "why will this succeed, where beanshell failed?"
That is, I had REPL style environments for java a long time ago. And literally nobody used it. I can see arguments for having the REPL being in actual Java instead of a shell subset. But, Java has a long way to go from bootstrapping something in a repl and automatically saving it to something that will work as a normal entry point. (Though, again, even JRebel has existed for a long time now.)
I doubt many programmers already using Jython, Beanshell, Apache Groovy, Xtend, Rhino, or Nashorn will change to the Java 9 JShell. I switched from Groovy to Clojure for scripty stuff a few years ago, mainly for the macros. A well-placed macro can avoid a lot of clutter in repetitive testing -- a far better solution than some heavy-weight testing framework.
If you want to be literal about it then sure, it just means read-eval-print loop. But I think that's akin to saying that a functional programming language is a language that has functions in it.
EDIT: To be clear, what I'm saying is that when people say 'I really love using Common Lisp because it has a REPL' they aren't saying 'I really love using Common Lisp because it has a prompt I can write raw strings of code into that executes that code and has no other features'. That's not a lovable feature.
People love Lisp REPLs because there's much more to them than that. In Lisp, the REPL is more like GDB than it is like Python's REPL.
Not really sure why the reaction to my comments here is so viscerally negative. Very few terms that we use are wholly literal. REPL isn't literal either.
Sorry if my comment with the definition of REPL seemed "viscerally negative". I understand that a good REPL has more features that just the bare bones, but you said that programs that read an input, evaluated it, and then printed the result aren't REPLs. You should've said that they aren't good or useful REPLs.
Your argument is the equivalent of saying notepad isn't a text editor because you can't edit multiple lines at once or highlight syntax. Those are features that good text editors have, but it does not mean notepad is not a text editor.
A 'command line interface', for example for bash, does the same. What is the difference to a REPL? Is it just another name for the same concept or are there differences? What do you think?
It is a subtyping relationship. Bash's command line is a (minimalistic) REPL, but not all REPL are like Bash's command line. In most contexts, talking about REPLs is implicitly talking about REPLs that do more than just read, eval and print, however illogical that may sound.
Note that few languages actually define READ in a user-friendly way (since python 2.6 you have the ast package, Bash's "read" returns strings).
Anything can have a REPL - I did one for C++ called UnderC. Unfortunately, a technological dead end and some of the worst Bison on the planet. But it was surprisingly pleasant to use C++ in a interactive way and I wrote a C++ book working from the idea that people can learn to program better in such a conversational context. Required some interesting flexibility in the usual grammar.
The compiled or interpreted nature of a language (or its runtime) is a fuzzy concept at best (Java is interpreted on the JVM in the sense that it emits bytecode that is interpreted by the JVM; the main implementation of Clojure JVM compiles Clojure to Java bytecode that eventually gets interpreted). The notion you're looking for is probably that of incremental compilation / running.
I think REPLs work better in languages that provide 1st class immutability support, since it's easier to set up state once, then play with functions without having to re-set up state every time you tinker.
I think REPLs work better in languages that don't provide 1st class immutability support, since it's easier to change state, then play with functions without having to re-set up state every time you tinker.
Or you could just make copies of state you want to keep before mutating it. Both mutable imperative code and immutable functional code can emulate each other, so which one to choose largely depends on your preferences.
Yes, Clojure(script)'s REPL experience is really amazing. I don't think there are any general-purpose languages (beside Common Lisp) actively being used today that even come close in comparison. What's comes with Python, Ruby, C#, JavaScript, etc. - is a feeble attempt of an interactive shell, those aren't really REPLs. Even with Haskell I was really surprised to find out that you can't "send" any selected piece of Haskell code onto GHCI.
I've never found a case where I've wanted to retain state between R sessions (I think doing so can be an anti-pattern).
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.
(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)