What makes a good REPL?(vvvvalvalval.github.io) |
What makes a good REPL?(vvvvalvalval.github.io) |
(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.
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.
(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')
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.That's the purpose of readtables in Common Lisp, defining custom readers for converting external representation to internal ones.
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.
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.
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.
[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.
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...
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.
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, I'd like to know what I'm missing.
That's why they are heretics !!
(Just joking of course... Scheme is the other great Lisp dialect.)
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.
>> 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.
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 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)
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.
[1] -- (https://www.manning.com/books/type-driven-development-with-i...)
That is, it may have originated with that meaning. But it is far from unique in having the general meaning shifted with use.
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.)
Then there is a big advantage on having it as standard tool, instead of something done by third parties.
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 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.
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 :)
[1] -- (https://github.com/millejoh/emacs-ipython-notebook/blob/mast...) [2] -- (https://python-pillow.org/)
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.
[1] -- (https://github.com/clojure-emacs/cider/blob/master/doc/debug...)
They are image based, so you can just save your session and continue using it later in another day.
> I think the author alluded to that. Of course I have no experience with Mesa/Cedar Oberon. I'll have to check those out.
On those systems, the unit of loaded code is a module and the whole OS only has dynamic libraries as executables.
So you can just reload a module and the next time you do module.proc on the repl, you will be referring to the newly loaded module.
For me an ideal REPL should be like the experience I used to have in Smalltalk.
I had no idea that common lisp had image saving! I only used the Carnegie Mellon one in college.... its been a long time.
Lisp has image saving since around 1960...
Is done on .NET/F#. I wonder how architect the thing so I can have a good repl yet compiled... but how?
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.
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.
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.)
>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.
No it's the equivalent of saying that not even programme that can possibly, technically edit a text file is a text editor.
Python's shell thing is not a REPL.
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).
LispWorks can:
* save images
* create optimized images/applications for delivery, using a treeshaker for removing unused stuff
* can generate Mac applications with the usual ceremony/ application bundles
* can generate shared libraries which can be linked into programs written in C or similar
Some other compilers can generate standalone C code doing whole-program compilation. For example mocl or some inhouse compilers used by companies.
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.
GHCi has this feature, if you explicitly turn it on (-fwarn-name-shadowning with -Werror). I agree that it's not a terribly useful way to run a REPL.
"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.
Right, just like the king's subjects don't actually need a toaster, but rather breakfast food cooker.
So def on an existing binding does assignment. That's what is meant by "re-binding".
~ $ 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
0Thats 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.
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.
> 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.
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...
Old demos from me:
https://www.youtube.com/watch?v=VU_ELJjbnWM
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).
Clojure is constrained by the JVM and its implementation. Though Java is even more constrained.
You get a mini Common Lisp Object System demo in the LispWorks REPL:
We define a class person with a slot 'name':
CL-USER 1 > (defclass person () ((name :initarg :name :accessor name)))
#<STANDARD-CLASS PERSON 402005BA03>
Let's create a list of persons: CL-USER 2 > (setf persons (mapcar (lambda (name)
(make-instance 'person :name name))
'("Jan" "Ralph" "Joan")))
(#<PERSON 40200A493B> #<PERSON 40200A49F3> #<PERSON 40200A4AAB>)
Let's define a custom print method: CL-USER 3 > (defmethod print-object ((p person) stream)
(print-unreadable-object (p stream :type t :identity t)
(write-string (name p) stream)))
#<STANDARD-METHOD PRINT-OBJECT NIL (PERSON T) 40200A942B>
How does a person print now? CL-USER 4 > persons
(#<PERSON Jan 40200A493B> #<PERSON Ralph 40200A49F3> #<PERSON Joan 40200A4AAB>)
Let's add a slot to the class, a slot 'age': CL-USER 5 > (defclass person ()
((name :initarg :name :accessor name)
(age :initarg :age :accessor age :initform 0)))
#<STANDARD-CLASS PERSON 41404737D3>
Let's update the print method: CL-USER 6 > (defmethod print-object ((p person) stream)
(print-unreadable-object (p stream :type t :identity t)
(format stream "~a ~a" (name p) (age p))))
#<STANDARD-METHOD PRINT-OBJECT NIL (PERSON T) 40201289FB>
Woops: all persons now have already got the new slot: CL-USER 7 > persons
(#<PERSON Jan 0 4140473733> #<PERSON Ralph 0 4140473933> #<PERSON Joan 0 4140473D3B>)
Let's set the new slot: CL-USER 8 > (mapc (lambda (p age)
(setf (age p) age))
persons
'(23 43 21))
(#<PERSON Jan 23 4140473733> #<PERSON Ralph 43 4140473933> #<PERSON Joan 21 4140473D3B>)
Let's define a new class: social-security-mixin: CL-USER 9 > (defclass social-security-mixin ()
((social-security-number :initarg :ssn :accessor ssn)))
#<STANDARD-CLASS SOCIAL-SECURITY-MIXIN 40200111C3>
Let's add this new class to the superclasses of PERSON. CL-USER 10 > (defclass person (social-security-mixin)
((name :initarg :name :accessor name)
(age :initarg :age :accessor age :initform 0)))
#<STANDARD-CLASS PERSON 41404737D3>
Now we do something really wild: we write an around method for printing: CL-USER 11 > (defmethod print-object :around ((p person) stream)
(print-unreadable-object (p stream :type t :identity t)
(call-next-method)))
#<STANDARD-METHOD PRINT-OBJECT (:AROUND) (PERSON T) 40200AB8A3>
We redefine the original method just to print the name and age of the person. CL-USER 12 > (defmethod print-object ((p person) stream)
(format stream "~a ~a" (name p) (age p)))
#<STANDARD-METHOD PRINT-OBJECT NIL (PERSON T) 40200AC9EB>
Then we define an AFTER method for the social-security-mixin class: CL-USER 13 > (defmethod print-object :after ((o social-security-mixin) stream)
(format stream " ~a" (ssn o)))
#<STANDARD-METHOD PRINT-OBJECT (:AFTER) (SOCIAL-SECURITY-MIXIN T) 402001917B>
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...
As for the JVM: https://common-lisp.net/project/armedbear/
Sooo its not the JVM.
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 :)
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...