[HN Gopher] On Repl-Driven Programming
___________________________________________________________________
On Repl-Driven Programming
Author : todsacerdoti
Score : 313 points
Date : 2021-01-03 07:35 UTC (1 days ago)
(HTM) web link (mikelevins.github.io)
(TXT) w3m dump (mikelevins.github.io)
| FraserGreenlee wrote:
| I don't see how this is different from using the pdb Python
| module? You can catch exceptions and use to write functions.
| hchz wrote:
| The biggest distinction I see is the ability to continue from
| exceptions.
|
| I find the other differences to be minor in comparison - to be
| able to recover and continue execution following post-mortem
| debugging is amazing.
| gumby wrote:
| Back in the early 80s a friend described this approach as
| "programming by successive approximation." I was stung by that,
| but I saw his point.
|
| Nevertheless I felt it was wrong. The approach of designing a
| data structure, making a few functions to manipulate it,
| designing another, making sure they work together, etc isn't
| really that different in lisp from C++. The loop is tighter when
| you can interactively test.
|
| Either way you have to think ahead.
| avl999 wrote:
| Is there a modern non-Smalltalk, non-Lisp (or its derivatives)
| non functional imperative programming language that supports a
| "real repl" that the author is talking about? I don't know if
| that kind of environment is for me but I'd love to give it a
| spin.
|
| I have never found the REPL (the Python/Ruby REPL which is not a
| "real repl" according to this article) to be that useful beyond
| quickly playing with API of a library with sample data and
| getting a feel of it and its return types. So this version of
| repl described in the article does sound interesting.
| tlarkworthy wrote:
| https://news.ycombinator.com/item?id=25620899 (JavaScript)
| dnautics wrote:
| Bash?
| eikenberry wrote:
| Shells in general are a great example IMO. They are what I'd
| maybe call repl-first environments, in that they are used
| primarily as interactive repls but can be used to write
| programs.
| tieze wrote:
| In Emacs you can set up Python in a (quite neutered) similar
| way. Whenever I change a function, I just evaluate the whole
| buffer to load it in the attached Python process, and will
| switch over to the Python process to play with it if need be.
|
| Also, Forth sports a repl.
| sgillen wrote:
| Julia certainly places a lot of emphasis on the repl. It
| doesn't have break looks that I'm aware of although I really
| wish it did!. It's a specialty language though IMO, if you're
| not doing something scientific I'm not sure it makes much sense
| to learn.
| CraigJPerry wrote:
| I'm thinking a SQL console mostly qualifies per the article's
| idea of a repl. The break loop concept isn't perfect there
| though.
|
| I've always liked the python repl, breaking out a django repl
| and being able to manipulate the db via the api always felt
| like a super power to me when coupled with the expressiveness
| of basic python but last year i started dabbling with Clojure.
| My first "real" repl driven development experience.
|
| 2 things stood out for me:
|
| 1. You end up with some genuinely useful executable
| documentation and it just makes sense to me to preserve some of
| what i did in the repl while developing. I saw this first here:
| https://github.com/stuartsierra/component/blob/master/dev/ex...
|
| 2. Integration with your editor is essential - having a
| keystroke to send a form to the repl or evaluate in-line is a
| real productivity boost. It's like writing code while an
| intelligent debugger is permanently live. You don't need to
| wait for a compiler run then parse the feedback, it's just
| instant in-line runtime feedback. "Paredit" is really sweet. I
| used calva in vs code for this and tbh it's as attractive as
| the language in some ways. I have no idea how to meaningfully
| apply those concepts to something like python or javascript
| though. They just don't lend themselves to that kind of
| structural editing.
| moocowtruck wrote:
| have you ever tried something like https://gtoolkit.com/
| cutler wrote:
| There's Hy (hylang.org) for Python which comes with a real
| repl.
| keymone wrote:
| Author describes a nice feature, but that's not what makes repl-
| driven development tick for me. Repl really shines when editor
| integrates with it - send a form to evaluate, instantly get
| result back into the editor, eval a form and replace it with the
| value received, define and redefine the environment on the fly,
| get instant feedback if your code works without having to drop
| into shell and run some test command, micro-tests and usage
| examples that you can actually run inside editor to see how they
| work.
|
| You can get similar integration with ruby and python, but there's
| way more friction because that kind of development is not the
| blessed way and so nobody bothers to make it work.
| qznc wrote:
| > If it now works correctly, then congratulations; you found the
| problem!
|
| This notion if "correct" is "finished the current example as
| intended". Running a suite of unit tests would give me more
| confidence.
|
| Even that is only for programming in the small and that is
| comparatively easy to programming in the large. I don't see how a
| REPL would help there.
|
| That means a REPL makes easy things easier and hard things are
| unaffected. Does not sound like a competitive advantage to me.
| coldtea wrote:
| > _Even that is only for programming in the small and that is
| comparatively easy to programming in the large. I don't see how
| a REPL would help there._
|
| Because the right way of programming in the large is
| programming in the small and combining things (e.g. functions,
| components, classes, whatever, etc.), not building some huge
| monolithic monstrocity (this is orthogonal to monoliths vs
| microservices btw).
|
| Plus, this "programming in the large", when it gets to, say,
| 10.000.000 lines of code will still have bugs and behavior you
| need to check/search for/and fix in individual parts, and this
| "restarting/dynamic change" REPL will still be a great tool
| here.
| djhaskin987 wrote:
| What a surprise, the functional folks are arguing that
|
| "Because your language is different than mine, therefore it's not
| as good as mine."
|
| I've heard people say that Java or C++ isn't a language, because
| it doesn't have features x-y-z when Racket does. Garbage.
| Similarly, this guy is saying that because Python's shell doesn't
| have breakloops, it isn't a _real_ REPL. Also garbage. 99.9% of
| the time when using REPLs /shells, if something doesn't run, I
| correct the code, copy and paste it into the shell, and run it
| again. Breakloops don't exist in the Python shell because they
| don't _need_ to. Python shell works about as good as Clojure REPL
| for me.
|
| I get really tired of these elitist holier-than-thou arguments.
| Functional programming is a thing. Procedural programming is a
| thing. REPL is a historical name... for an interactive language
| shell. They really are quite equivalent.
| mikelevins wrote:
| You misunderstand me.
|
| I am not saying that your language or your way of working is
| not as good as mine. I'm not saying that you or anyone else
| should discard your ways of working and adopt mine.
|
| I'm not saying that Python's repl isn't actually a repl. I'm
| not saying that everything needs to have breakloops. I'm not
| saying that I or anything I like is any holier than anyone
| else.
|
| What I'm saying is I like a specific style of programming and
| it makes me happy. I think more people should know about it
| because it will make some of them happy, too. I would like
| that.
|
| Besides just liking to make people happy because that's the
| kind of personality I have, I would also like it if demand for
| tools that support my preferred way of working increased, so
| that there are more of them available over time rather than
| fewer.
|
| And, finally, I like to use the phrase "repl-driven
| programming" for the style I like because some time ago someone
| asked about what distinguished old-fashioned Lisp and Smalltalk
| environments from others that the questioner was more familiar
| with, and "repl-driven programming" was a phrase that person
| used. So I adopted the terminology. I think it's a reasonably
| good piece of jargon for its purpose.
|
| We can sensibly distinguish a repl-driven environment--that is,
| an environment whose design is driven by the requirements of
| interactive programming with a read-eval-print loop--from an
| environment that merely has a repl as one of its affordances.
|
| If the distinction isn't meaningful to you, then presumably
| "repl-driven" design isn't either, and you're presumably not
| one of the people I'm talking to.
| tmsh wrote:
| Having gone through Rustlings these past few days (via
| https://users.rust-lang.org/t/best-way-to-learn-rust-program...),
| I feel like the speed of the Rust compiler has gotten a lot
| faster since I looked at it a few years ago.
|
| And the compiler messages (once you get the hang of it; and the
| investment in learning the syntax is so trivially worth it in the
| long run) are so much richer and more helpful than other
| languages. So it really does "feel" like a REPL or most of the
| way there as a REPL (and I have one of the few wikis at our work
| advocating for Common Lisp that I wrote a year ago, and still
| actively follow Clojure groups, etc.).
|
| It's just Rust starts and runs so fast (and Cargo makes it easy
| and clear like Go); so that being able to re-run periodically
| really fast is like a REPL. Yeah there's no retained state or
| ability to keep a REPL running for days, etc. (though Tmux.. and
| __nix as a repl.... more on that later), but it 's reached a
| point where compilation is an OOM faster than before, so the loop
| of: read, eval, print now can include the compiler.
|
| And you get this amazing boost where if you're operating e.g. on
| large data (if you're a senior engineer, like Dan Luu blogs
| https://danluu.com/, you're often scanning codebases or log files
| or other metadata to answer questions); so being able to tweak
| and re-run even without caching at OOM faster speeds than other
| languages is wonderful.
|
| I can create complex refactoring scripts in Rust on GBs of data
| and constantly re-run them until it's just right.
|
| To my knowledge you can learn the ins and outs of CL or Rust;
| both fairly specific-knowledge-heavy languages at first (Rust
| with some syntax, CL with its non-uniformity of functions). It
| just seems like Rust is the future. It sort of "merges" dynamic
| languages with REPLs and *nix (with tmux) as the REPL. Pretty
| exciting.
|
| And if someone wrote a CL-like REPL with something like SLIME/SLY
| for debugging for Rust it'd be kind of game over. And I could see
| that coming in the next few years
| (https://github.com/google/evcxr seems cool, but perhaps the
| dependency on Jupyter complicates it a bit).
| twobitshifter wrote:
| For Ruby users, pry gets you most of the features discussed in
| this post. You can get the error break loop, and drop into the
| live environment with binding.pry. Code can be added and
| expressions adjusted at this point. It's very powerful and the
| only "debugger" I've ever needed for Ruby.
| mikelevins wrote:
| I've used it a good bit. When I'm using it, I miss the tools I
| have in Smalltalk and Lisp environments.
|
| When I use Smalltalk or Lisp, I don't miss pry or the other
| tools I have when working with Ruby.
|
| I do like Ruby, though. I probably like it better than any
| other language that isn't made of s-expressions. (Well, I like
| ML a lot, too.) But the whole time I'm working with it it just
| makes me want to write another Ruby implementation, but this
| time on top of a proper interactive runtime.
|
| There is one, actually: MagLev, which is built on a Smalltalk
| runtime. As far as I can tell, though, it's dormant.
| civilized wrote:
| How is this different from, say, programming in Python and being
| forced to drop into a debugger whenever there is an error? The
| debugger also lets you see everything in the stack, modify
| definitions, etc.
|
| MATLAB and R both have options to drop you into a debugger if
| there is an error. Not sure if Python's debugger can do this, but
| other than that, this style of programming is perfectly doable
| outside of Lisp and Smalltalk.
| mikelevins wrote:
| So go ahead and set up that working situation and try this:
|
| Define a function foo() in one module that calls a function
| bar() in another one, but don't define bar().
|
| Now call foo().
|
| Now, in the debugger that you break into, give a definition for
| bar(), and resume the execution of foo(). Don't quit the
| program and restart it; that's cheating!
|
| If that works fine, please tell me what tools you're using,
| because they'll materially improve my tools for some work I
| need to do.
|
| Also, try this:
|
| Define some classes and make a bunch of instances of them.
| Write a few methods on the classes. Start your program running
| with code that uses those classes and methods.
|
| Now change one or more of the class definitions. Don't stop and
| reload the program! Again, that's cheating.
|
| When you land in the debugger (you do land in an interactive
| debugger, right?) use it to inspect the stack and find out
| which classes and methods are on the stack and what's
| responsible for the breakage. Ask the Python runtime to show
| you all the functions, types, and values that are on the stack,
| and when you find the likely culprit, ask it to take you to the
| source for the version that's currently on the stack.
|
| Now, while you're still suspended in the call stack, redefine
| the offending classes and methods and tell Python to use the
| new ones. Now resume the broken computation. If you hit another
| problem, use the same tools to find and fix that one, too.
|
| After you've done all that, please let me know what your Python
| toolchain is so that I can use it, too.
|
| Also, if that's all perfectly doable in Matlab and R, that's
| good to know. Let me know about those tools, too. I haven't had
| much occasion to use them, but that might have to change if
| they work that way, too.
| tylermw wrote:
| I don't know about MATLAB, but everything you describe is
| trivial to do in R. Being dropped into the debugger is
| standard on most R installs (it's an option you can turn off)
| and everything you mentioned can be done in the REPL.
| mikelevins wrote:
| That's good to know; thanks!
| dm319 wrote:
| This is what makes R (and Julia I think) so special (where
| everything evaluates to an S-expression). Having REPL for
| exploratory data analysis is especially useful.
| zmmmmm wrote:
| I have a concern about REPL driven development, which is around
| how maintainable the result is.
|
| The reason people like REPLs is because they can experiment to
| discover the right way to write the code in a very fast feedback
| loop. This alleviates a huge pain when you are operating with
| uncertainties or at the edge of your knowledge - 'does this
| function return null if no matching entries or an empty list?' -
| etc.
|
| The problem is that the reader of your code is not going on that
| journey with you. They are coming in naively without knowing all
| of that experimentation you did. The writer now has the
| opportunity to create code that "just happens" to work by a magic
| of coincidences and conveniences based on very subtle non-obvious
| properties of the APIs and systems they are working with. The
| reader is severely disadvantaged and will have a much harder time
| to reach parity with the state of knowledge that the writer had.
| For example, some property of a function or object may not be
| documented at all, but through the REPL the writer has
| ascertained it is true. How can the reader know this?
|
| So its a double edged sword in that the more you empower the
| original creator of the code, the greater imbalance you create to
| the later readers and maintainers of the code. Used well and with
| great discipline I could see it being very powerful and positive
| force to make better code. But in the worst case scenario, you'll
| end up with extremely unmaintainable code.
| IlliOnato wrote:
| I think your concern is valid, but I think it's the general
| case with any powerful tool: it can be abused, or shoot you in
| the foot, and requires discipline to apply.
|
| I don't have REPL-driven development experience, but I think we
| are all familiar with StackOverflow. This is a great, great
| resource, but it lead scores of programmers to the "style" of
| development, where they mindlessly copy solutions from SO,
| without understanding of how and why they work.
|
| I guess one can extend the old maxim "there is not now, nor
| ever will be a programming language that makes it easier to
| write a good program than a bad one"...
|
| (edited grammar)
| discordance wrote:
| Could this issue of expressing the journey be addressed by
| leaving code/markdown breadcrumbs in Juptyer notebook like
| cells?
| reddit_clone wrote:
| When a certain thing is done in repl, it usually converts into
| a function or two in the source code. The ad-hoc testing code
| is converted into unit testing.
|
| That should help with maintenance no?
|
| One big theoretical advantage I see in repl driven development
| is, I can be sure that _all_ the code has been executed at one
| time atleast. (In a normal edit-compile-link-test language
| cycle, I can not be sure of that)
| Scarbutt wrote:
| Sure, but the point is that it requires more discipline,
| since the REPL can encourage you to be more lazy/sloppy.
| xapata wrote:
| Yep, just copy-paste a bit of REPL history for a docstring,
| then use `doctest` to make sure it holds. Pretty soon you'll
| have ~100% test coverage. Minus the error paths you didn't
| test in REPL.
| tareqak wrote:
| If there was a way to "playback" the REPL and store it for
| later, then it would be possible for the subsequent users (or
| even the same user) to see how the person using the REPL got to
| that point. It would not be a replacement for documentation,
| but it could be in addition to documentation or even a starting
| point.
| heresie-dabord wrote:
| I agree with you that the immediate start-up and feedback is a
| great benefit to the coder. This is why I dislike complex,
| Rube-Goldbergian REPL systems.
|
| There is a use-case for a throw-away interaction with a REPL.
| For example, how does $builtinFuncX work, or how would $data
| best be imported into a structure?
|
| A REPL can also be a good initial approach to a more ambitious
| problem. In this case, a REPL can be good for focus and
| discipline.
|
| If the second case is going to answer your concern and be
| constructive, it's necessary to be able to build the code for
| sharing and cleanly export the code for re-use.
|
| I've had success tackling challenges using REPLs for Python and
| Perl [1] in both ways. But no tooling is going to solve the
| problem of a sloppy teammate who claims success just because
| "it compiles" and "it works on my box". A person who knows how
| to build good tooling goes further.
|
| [1] https://github.com/viviparous/preplish
| koz_ wrote:
| The author of the code is going to have to discover how these
| APIs work somehow, right? Why would the code end up worse if
| the author has access to a particular tool for interacting with
| these APIs? A shell and curl is a REPL for interacting with and
| exploring HTTP APIs but I don't think anybody would claim that
| the use of such tools makes code written to those APIs harder
| to follow.
|
| In fact, one of the benefits of your REPL being integrated with
| one's editor, and being in the language of the system, is that
| the kinds of ad-hoc scripts you write when doing exploratory /
| debug coding which one would typically discard after use can
| often times simply be committed, either as utility functions or
| test cases. Far from pulling up the ladder on devs who come
| later, having a REPL makes it more likely for them to have
| access to the same tools used to build and understand the
| system in the first place.
| marcosdumay wrote:
| More than Lisp, SQL is a REPL-only language. The complexity you
| can achieve there without some interaction is very small.
|
| After some on the field experience, my conclusion is that this
| is not a large problem. Yes, you were not there on the initial
| design and does not have all of that acquired knowledge, but
| you can always run your own tests and formulate and verify your
| hypothesis. The REPL is there for debugging too, not only for
| the original design.
| tester756 wrote:
| >SQL is a REPL-only language. The complexity you can achieve
| there without some interaction is very small.
|
| ha ha.
|
| unless it's giant sql with 5 sub queries and poorly formated
| for bonus points
|
| good luck
| marcosdumay wrote:
| Hum... 5 sub queries? Nope, I'm talking about bigger stuff.
| juststeve wrote:
| 100%, and that also applies to the original author.
|
| Building a script from the REPL might mean running different
| parts of the code out of order (such as reloading a function
| definition). As you mentioned, it's easy to lose track of
| state, writing 'clever'/unmaintainable code, and forgetting the
| order and purpose of the code.
| Buttons840 wrote:
| Code developed in a REPL would ideally be "pure", meaning it
| takes a value and returns a value without side-effects. Then
| others can experiment with it to their hearts content. Maybe
| it's ok to _read_ from a database, but don 't write. If you
| write to the database nobody else will dare touch your code.
| mikelevins wrote:
| That rule absolutely does not work for me.
|
| For me, the whole point of the repl is that I want to build
| something incrementally, interactively, building it a little
| at a time by making small changes to it. I want it in memory,
| running and responding to my changes as I make them. Put this
| here; put that there. Change that around. Let me look at it;
| nope. Put that back where it was. Now add this.
|
| If the repl is stateless then the whole purpose is defeated.
| Buttons840 wrote:
| I don't mean the REPL is stateless, but the code developed
| using the REPL would ideally be stateless. Stateless code
| would be easier for others to experiment with in their own
| REPL, without "oh no, you broke production with your REPL!"
| mikelevins wrote:
| You can use a stateful repl to develop stateless code,
| but in order to provide the features of an old-fashioned
| Lisp or Smalltalk system, the kind of system I prefer to
| work with, you can't make any stateful changes off limits
| because there's no way to know in advance what changes
| you're going to need to make.
|
| As you're rummaging around through the dynamic state of
| the running system, you'll discover changes that need to
| be made, and you might discover them absolutely anywhere.
| Sure, you could always kill the running system, make the
| change to the sources, and rebuild the system, but that's
| exactly what we're trying to avoid.
|
| Consequently, old Lisp and Smalltalk systems are allergic
| to restrictions on runtime changes. Loosely speaking, if
| I find something I can't change while my program is
| running, that restriction is a bug in my development
| environment.
|
| Old systems like this will _discourage_ certain kinds of
| changes because they 're usually ill-advised, but will
| not _forbid_ them, because forbidding them is anathema.
|
| As an example, several Common Lisps implement __package
| locks __on certain system packages. A __package lock
| __prevents you from changing the definitions of system-
| defined constructs.
|
| But it's Lisp, so it doesn't _really_ prevent the change.
| It just makes it more inconvenient. You have to say
| "Mother, may I?" first, which gives you the opportunity
| to soberly consider whether making that specific change
| is really really what you want to do.
| Jtsummers wrote:
| You don't connect (necessarily) the REPL to production.
| The code can be stateful code if you're not careless.
| Just like you oughtn't log into the production database
| server and write/update/delete data entries directly.
|
| This is about the development phase more than the
| production phase. Do something like this (directly in the
| REPL or in a buffer as mikelevin has described doing
| elsewhere): (defparamater *db* (connect-
| to-test-db)) (query-db *db* (make-query some-query-
| description)) ;; see that it pulls out the desired
| record (let ((record (query-db *db* (make-query
| sqd))) (update-record record) (write-to-
| db *db* record)) ;; update the entry ;; rerun the
| query above to see that things changed as expected
| ;; rewrite that let as a defun: (defun make-update-
| to-some-record (sqd db) (let ((record ...))
| ...)) ;; test that it works (make-update-
| to-some-record sqd *db*) ;; see that it does in
| fact do the same as the let above. ;; move that to
| a permanent source file, and build it ;; into the
| system. ;; move tests into test file. ;;
| repeat with next feature/task
|
| The functions run in the REPL are stateful, but we aren't
| careless about what they're touching.
|
| NB: You _can_ connect to production environments. You can
| even connect to live, production lisp images. This could
| be useful for: profiling real-world activity, debugging
| real-world problems, applying hot-fixes. But especially
| that last one should be done carefully, and ought to have
| been validated using a test environment first.
| reddit_clone wrote:
| Right. I usually start with creating global objects in
| the repl. Write little snippets in the repl to operate on
| these. The snippets end up with all these objects
| parameterized as functions. Which I can test again from
| the repl itself.
|
| Turn the testing code into unit test as I go along.
|
| Rinse. Repeat.
| dragonwriter wrote:
| > For example, some property of a function or object may not be
| documented at all, but through the REPL the writer has
| ascertained it is true. How can the reader know this?
|
| Well, the writer could document their code.
|
| Or, the reader could discover behavior of undocumented code the
| same way the writer did; presumably the "reader" will also have
| a REPL available.
| thewebcount wrote:
| > But in the worst case scenario, you'll end up with extremely
| unmaintainable code.
|
| Actually, isn't it far worse than that? Say you're stopped at a
| function call, you call it, and it does the wrong thing. You
| edit a variable that's passed as an argument to the function
| and call it again. Now it does the right thing. Great! You
| fixed the bug, right? Except that you don't actually know that
| your program can actually ever end up in that state naturally.
|
| Where did that variable originally come from? Was it entered by
| the user? Was it read from a file? Was it from a table? If so,
| does the table contain the modified value? This seems like a
| recipe for problems to me. I'm sure it's fine in simple cases
| where you're just changing the value of a constant or
| something, but it seems really likely to lead to incorrect
| reasoning about the code while you're working with it in any
| but the simplest of cases. Am I blowing this out of proportion?
| mikelevins wrote:
| Well, no, you didn't fix the bug. You collected a datum about
| the bug: it caused a variable to have a value that it should
| not have had. Now you know something about it that you maybe
| didn't know before.
|
| When you say "Great! You fixed the bug, right?" it sounds
| like a paraphrase of a passage in my essay that I had some
| misgivings about when I wrote it. But I thought, surely
| nobody's going to think I really mean the problem is
| definitely solved? Surely, people will realize its a bit of
| hyperbole.
|
| Maybe not. Maybe I should have instead written it more
| soberly. Maybe I should have just said "Now you've collected
| a bit of data about the nature of the bug."
| discardable_dan wrote:
| I have worked on massive LISP projects that were developed
| using REPL-driven development, and your fears are critically
| valid. Even worse, this basically hamstrings it as a standalone
| application. The workflow to _use_ the application becomes
| "start the REPL under emacs and launch it", which is, in my
| personal opinion, a completely unacceptable distribution model.
| And if you don't use emacs, good luck to you! Once set up as
| REPL-based development, Smalltalk and LISP both have to
| essentially ship their "whole environment" to deploy the
| application. This only works out well for small projects that
| do not need to be distributed, or whose distribution you, the
| developer, have a lot of control over.
|
| (There's an aside to be said about building binaries from a
| LISP compiler, but most binaries I have seen produced by such
| compilers still need access to the required libraries, which
| sort of hoses it as a distribution solution. This could be a
| result of the build system used on the projects I work on.)
|
| And your documentation concern is valid, but I think even worse
| is the design concern: when designing an API, you have to stop
| and design it and consider the various concerns. If you develop
| it as you need it / as you go, it is possible to arrive at sub-
| optimal designs. Hacking the "next piece" out at each step is
| likely to lead to slanted designs with poor APIs, requiring
| future refactoring. You see this with programmers at every
| level: if you ask them to write a program to do a single thing,
| then add more (related) capabilities, and continue ad nauseum,
| at some point the program will need to be refactored. This
| suggests that, without prior planning, REPL-based design is set
| up to incur technical debt. Unfortunately, my real-life
| experience confirms this. Moreover, refactoring something in a
| REPL is... a chore, to say the least.
| mikelevins wrote:
| Your observations are valid, and need to be kept in mind when
| developing in an exploratory, interactive way.
|
| Your alarm seems a little overblown. Maybe it reflects a
| particular bad experience? I've delivered a bunch of products
| built in the way I prefer to work, and it's been a long time
| since I've had the problems you're describing. Maybe that's
| because I ran into such such problems early in my career and
| learned ways of handling them.
|
| And yeah, if you just iterate and explore, you can wind up in
| lacunae. You do need to develop some actual goal and a vision
| of how to get there. Interactive development can be really
| helpful for the exploring part, but it can't pick your goals
| for you.
|
| In short, interactive development is a way of working that's
| good for some cases and some people (me, for instance). It's
| not a cure-all or an objectively superior methodology for all
| people or all cases. I don't want it to take over the world.
| I just want it to continue to be an option because it's the
| way I prefer to work.
| Folcon wrote:
| Echoing this, I'd really like it to be an option in other
| languages, because it's just a pleasant way to work and
| it's nice to have the option.
| O_H_E wrote:
| Oh man, I appreciate your pragmatism and open-mindness. It
| is refreshing to see someone advocate for something without
| trying to "take over the world" and proving others wrong.
|
| Thanks for sharing your experience amd positivity with the
| world.
| TheAnswerMan wrote:
| So REPL may make people less likely to document the code?
|
| I don't see the correlation.
| avmich wrote:
| > the more you empower the original creator of the code, the
| greater imbalance you create to the later readers
|
| REPL gives you more tools to shoot yourself in the foot, but
| you're not required to use those. Most programmers can write
| spaghetti code in many Turing-complete languages - not that
| they do that all the time.
|
| In other words, REPL gives you powerful tools - which, yes, can
| be misused. They are still powerful, and can bring success when
| used well.
| wtetzner wrote:
| I really don't follow. If you're programming in a language
| without a REPL, don't you have the exact same problem? If the
| author doesn't document their knowledge, then it's lost for
| readers.
|
| I don't really see how a REPL makes any difference there.
| alknemeyer wrote:
| For what it's worth, IPython's "autoreload" magic and Julia's
| "Revise" package get you _closer_ to what the author describes.
| In both cases, one can define functions/classes/structs in a
| file/module, load it in a repl, create objects etc, modify the
| file and have the changes propagate through to live objects
| joshlk wrote:
| You can also add breakpoints which automatically occurs when
| exceptions are raised. Which is similar to the breakloop
| functionality mentioned in the article.
| anentropic wrote:
| I find ipython + ipdb super useful in this regard
|
| But AFAIK neither provide the feature described in the
| article where you can continue running after an unhandled
| exception.
| jmuhlich wrote:
| True, but running the %debug magic command in IPython
| immediately after an unhandled exception drops you into the
| debugger at the point of the raised exception, complete
| with the full interpreter state including the stack. It
| doesn't permit edit-and-continue but you can still evaluate
| expressions (including calling any function) to explore
| what went wrong without having to recreate the situation
| inside an explicit pdb session.
| djm_ wrote:
| Yes, running pytest with the --pdb flag will drop you into
| the debugger on an unhandled exception which gets a
| comparable workflow but it's not quite the same as, a)
| writing tests to file first is not repl-driven development,
| and b) you generally have to think about doing it first.
|
| In an ideal repl-driven world you could write the test in the
| repl entirely and commit it to disk once you're ready.
| globular-toast wrote:
| Closer but still so far. As soon as you have more than one
| module in Python it completely falls apart, despite any
| autoreload magic. It just isn't the same at all as REPL based
| programming. In Lisps you can write your entire program with an
| instance and a REPL running the entire time. No restarting the
| instance required. It works because it's just how Lisp works. A
| REPL is a trivial thing you can write yourself that runs in the
| same instance.
|
| Whenever I think about it I can't quite put my finger on
| exactly what Python would need to make it the same. It's
| something to do with the way imports and namespaces work,
| though.
| sedachv wrote:
| > Whenever I think about it I can't quite put my finger on
| exactly what Python would need to make it the same. It's
| something to do with the way imports and namespaces work,
| though.
|
| Python has a fundamentally broken module system, and Python 3
| did nothing to fix it:
|
| https://nedbatchelder.com/blog/201908/why_your_mock_doesnt_w.
| ..
|
| https://docs.python.org/dev/library/importlib.html#importlib.
| ..
| globular-toast wrote:
| Right. That's it. Thanks for the links!
| miccah wrote:
| I admit I have not experienced true REPL driven development as
| the author defines it, but I have created an easier way for
| "development with a REPL" in vim. I love it because it's like a
| looser, faster Jupyter notebook, and it works with any language
| that has a REPL.
|
| It works by sending highlighted text to vim's terminal buffer. I
| wrote a short blog post with a demo and the vimscript code here:
|
| https://mcastorina.github.io/posts/vim-repl-driven-developme...
| miccah wrote:
| I should have read more comments before posting, but it seems I
| reinvented an inferior wheel:
|
| https://github.com/jpalardy/vim-slime
| magv wrote:
| For those of us that don't use Lisp (or Emacs+SLIME), and are
| stuck with Python/Julia/Lua/etc and Vim may I give a practical
| recommendation? The Vim-Slime plugin [1] is a half-decent way of
| making interactive development work. With a bit of tuning you can
| make it start a terminal emulator window inside Vim, and have it
| send the current paragraph of your source code into it with a
| press of a button.
|
| While not as great as working with a proper Repl-oriented
| language (as the article explains), this is still so much more
| pleasant than having to re-run the whole program every time you
| change a function.
|
| [1] https://github.com/jpalardy/vim-slime
| alpaca128 wrote:
| > With a bit of tuning you can make it start a terminal
| emulator window inside Vim, and have it send the current
| paragraph of your source code into it with a press of a button.
|
| Defining keybindings for splitting the window, opening a
| terminal buffer and copying a paragraph does not really require
| a plugin. That's one of the features I like most in Vim, its
| ability to just map any input to another sequence of keypresses
| can replace whole scripts and plugins provided you're fine with
| readability comparable to regular expressions.
| dm319 wrote:
| And nvim-R for vim/nvim and R.
|
| [1] https://github.com/jalvesaq/Nvim-R
| pjmlp wrote:
| Without caring about VIM, this would be my options,
|
| Python IDE tooling on Microsoft stack.
|
| https://docs.microsoft.com/en-us/visualstudio/python/python-...
|
| Juno IDE for Julia
|
| https://junolab.org/
| nikhizzle wrote:
| I also recommend jupyterlab for a similar experience. I was a
| hardcore vim terminal guy for 17 years, but now I won't be
| going back.
|
| The ability to interactively develop functions in almost any
| language inside the same environment had been revolutionary for
| my productivity given my short attention span.
| alpaca128 wrote:
| If you're using Jupyter within the browser you can have both
| via the FireNVim plugin in Firefox. It connects to NeoVim in
| the background and lets you edit any multi-line text field
| with 100% of your personal Vim configuration, the only
| limitation is that the color scheme cannot be changed.
| Jugurtha wrote:
| Would you mind sharing what you're doing with JLab? Do you
| use it in machine learning or are you doing something
| completely different?
| GianFabien wrote:
| AFAIK both Lisp and Smalltalk environments are image based. That
| is, all source code and all objects are stored in memory. You
| need to save that image in order to continue from where you left
| off. Snapshots are also used to allow rollback to previous "good"
| states.
| pjmlp wrote:
| Dart, Java and .NET have a kind of edit-and-continue without
| images.
|
| It is all a matter of tooling.
| andybak wrote:
| > It is all a matter of tooling.
|
| I think the point of the article is that tooling can only get
| you so far. The abstraction they attempt to provide is leaky
| if the support for this style of development isn't firmly
| designed into the entire system.
| pjmlp wrote:
| Yeah, but in that regard developer culture is what matters.
|
| I have seen very few people actually using these kind of
| workflows back when Smalltalk and Lisp were more relevant
| (I used Smalltalk/V back then).
|
| It is like using gdb, many don't go beyond step, next,
| print, run, breakpoint and discover how powerful it
| actually is (same applies to other debuggers).
| mikelevins wrote:
| I am indeed claiming that tooling only gets you so far,
| if you don't have good support designed into the runtime.
|
| I take you at your word that you didn't use the sorts of
| workflows I've described, but I did, and I still do, to
| the extent that I can, and it was common place among, for
| example, the programmers working on the bauhaus OS or the
| ones working on the SK8 development environment.
|
| We kept images around as a matter of course for various
| purposes, for example.
| phoe-krk wrote:
| _> That is, all source code and all objects are stored in
| memory._
|
| Common Lisp usually stores code in form of normal source files
| that are then possible to load into a clean-slate Lisp image.
| It's possible to dump images and restore them, but it's not the
| norm of working with CL.
|
| Unlike in Smalltalk, I currently know of no tools that allow
| one to easily edit source code of functions/methods that have
| already been compiled into the system, and therefore the
| dependency on the filesystem source files is heavy and
| immediate.
|
| AFAIK dumping images in CL is mostly used for shortening load
| times by preloading code and data, and for application
| delivery, but not for live editing support.
| brainbag wrote:
| It's not compiled, but Ruby's Pry REPL allows you to edit and
| update code files during live execution. I use it all the
| time for adding breakpoints and live fixing issues. It's very
| productive for problem solving.
| lispm wrote:
| > Unlike in Smalltalk, I currently know of no tools that
| allow one to easily edit source code of functions/methods
| that have already been compiled into the system
|
| Lots of Common Lisp systems support that in some way. Those
| record the location of the source code together with the
| machine code. These locations are stored in the image.
|
| For example the standard CL function ED takes a function name
| and in many Lisp systems this will edit the function in some
| implementation specific way. In Emacs one uses M-. to get the
| definition of a function. In those implementation it will ask
| the Lisp for the location of that function source code.
|
| The one system that worked similar to Smalltalk is Interlisp.
| See https://interlisp.org . It recently has been open sourced
| and is in the process of making it more accessible. It's a
| glimpse into an alternative world of computing from the past.
| phoe-krk wrote:
| I know that many implementations record source location,
| but the source itself is still on the filesystem, not in
| memory, therefore making source locations useless if we
| move the filesystem out of the equation.
|
| I also know that many implementations store the forms in
| FUNCTION-LAMBDA-EXPRESSION, but as I said, I know of no
| _easy_ way to edit these in-memory forms either. Interlisp
| has had a structure editor to work with those, but that
| utility was not ported back into the CL workflow. I hope it
| eventually will be, with Interlisp being open sourced now.
| lispm wrote:
| > therefore making source locations useless
|
| Just make source available on the file system. Mount it.
| Copy it.
|
| That's why Common Lisp has logical pathnames. Typically
| one uses logical pathnames in source locations. When I
| set up my application on a machine, I define a
| translation table to point to the source code.
|
| One can also just use standard pathnames and mount the
| source code to a standard path. That's how one also has
| set up Lisp systems in clusters. Every machine mounts the
| source in a standard path (or a logical path) and a
| client system will then have access to that source code.
| The image still has the recorded locations.
|
| Btw., even Smalltalk stores its source code not in the
| image. The Smalltalk source code is stored OUTSIDE of the
| image in the file system and it has to have the files
| available and needs to know the location of these source
| files.
|
| See: https://squeak.org/downloads/ The
| Squeak/Smalltalk programming system consists of three
| parts: * a virtual machine for your
| platform, * both image and changes files of a
| particular version, and * a sources file for the
| particular image file.
|
| The source and changes files contain the source code of
| the running Smalltalk. An edit of source code will then
| lead to an entry in the changes file.
|
| Smalltalk usually can decompile byte code, so one can
| edit code which has no corresponding source code, with
| some loss of original source information.
| dragonwriter wrote:
| > The catch is that the designers of your language system had to
| think that facility through in the planning stages. You don't get
| a decent implementation of it by bolting it on after the fact.
| Breakloops need full access to the entire development system,
| interactively, with a computation and its call stack suspended in
| the breakloop's environment.
|
| Interestingly, while Ruby's default REPL (irb) doesn't have this,
| and Ruby's (and irb's) designers obviously _didn't_ think this
| facility through in the planning stages, Ruby _does_ have an
| alternate REPL (pry) which has a lot of the advanced REPL
| functionality irb is missing but _also_ doesn't have this
| function built-in, but which does have a separate add-on (pry-
| rescue) which provides it.
| milansuk wrote:
| REPL is very useful, but few months ago I realize[0] that setting
| a break-point is not easy as people think, because the program
| can visit the line through multiple paths(function can be a call
| from different places), so you can spend a lot of time
| debugging/finding the right path to the line, which means that
| you probably have to set more break-points to get the program to
| the right place faster.
|
| [0] https://skyalt.com/blog/repl.html
| aidenn0 wrote:
| In lisp I do something like: (when (in-state-i-
| care-about) (break))
|
| Many non-lisp debuggers have conditional breakpoints, though
| they often have a larger performance overhead than the above.
| khalilravanna wrote:
| What are people's thoughts on how using vs not using a REPL help
| or hinder one's _thinking_ as a programmer. Specifically I mean
| if you have no REPL and maybe a long compile time you're forced
| to put a little more thinking and planning in up front if you
| don't want to waste your time. You might be more meticulous in
| catching bugs. Whereas with a REPL you can throw stuff at the
| wall. If something breaks you can just tweak variables and
| structure in real time till it starts working.
|
| That's just one characterization I came up with off-hand and it
| may be inaccurate. I'm mostly trying to paint a picture where
| tools affect the thinking of the programmer.
|
| What are people's thoughts on this? Does using a REPL or not
| using a REPL change your thinking and how so?
| mikelevins wrote:
| I think it's a valid observation. Circumstances that make you
| think things through ahead of time compel you to learn things
| and gain skills that you would not otherwise learn and gain.
|
| The other way around is true, too, of course.
|
| But the intensely-interactive style of programming-by-teaching
| is the less common approach. In pretty much every thread I've
| been part of about the topic there have been commentators
| who've said they just weren't even aware of it as an option,
| who had no idea that full-featured repl-driven environments had
| ever existed or were a possibility.
|
| What are the odds that such an obscure approach to programming
| has already attracted all the programmers who would benefit
| from it? Long odds, I'm guessing.
|
| So my guess is that it's worthwhile to point out the option for
| the sake of people who would benefit from it but don't know
| they have the option.
|
| For my benefit too, of course. I want more people to know about
| it, because that increases the chances that demand will rise.
| Rising demand increases the chances of greater investment in
| those kinds of tools, and reduces the chances of their
| extinction.
| Jtsummers wrote:
| > Specifically I mean if you have no REPL and maybe a long
| compile time you're forced to put a little more thinking and
| planning in up front if you don't want to waste your time
|
| Amusingly, to me, this is something people describe as the
| difference between having to submit punch cards to (or schedule
| a job on) a mainframe versus having a compiler on your own
| machine. That having such quick access to the compiler would
| lead to the "throw stuff at the wall" approach.
|
| IME, faster feedback loops _do_ increase the "throw stuff at
| the wall" approach, but for those of us who still (mostly) sit
| back and think, it's an enabler and not (just) a crutch. I can
| get in the flow much better in a language like Lisp and stay
| there. If, for instance, there's some confusion in my mind
| about how a function or data structure works, in C++ it takes
| me longer (more of a constant factor longer rather than orders
| of magnitude longer) to write something and test it out
| (assuming documentation doesn't clear it up for me). But while
| the time to explore it isn't _huge_ , I've been pulled out of
| my focus for longer. With Lisp, I can test it in seconds and
| get right back to whatever I was doing.
|
| But also, when I don't have a REPL, I break my programs into
| smaller programs (libraries/modules) which can be composed into
| the larger program I want. This lets me, partially, recreate
| the REPL experience. Using a test runner (a proper one or an ad
| hoc one) and a set of small CLI apps that let me use the
| smaller modules directly I get something approaching the
| feedback speed of the REPL. With fewer dependencies for any
| part under test (or as a CLI app), I get much faster
| compilation speeds vs needing to recompile a much larger
| program.
| blunte wrote:
| This article helps us understand what real repls enable that
| interactive language shells do not.
|
| What would be very helpful next would be a video comparison of
| writing a program that is just complex enough to not be trivial
| or too artificial, first using the common approach, and next
| using repl-driven approach.
| globular-toast wrote:
| Why not just learn a Lisp yourself? You'll get far more out of
| it than watching a video. The old adage is still true: learning
| a Lisp makes you a better programmer.
|
| Your choices are:
|
| * Common Lisp: install emacs, SBCL and set up SLIME,
|
| * Clojure: install emacs, Clojure and set up CIDER,
|
| * Scheme: install emacs, racket (or guile) and set up Geiser,
|
| * Emacs Lisp: install emacs.
|
| You'll notice that they all involve emacs. There are probably
| other ways but you want something that is quite tightly
| coupled, which all of those emacs packages provide. REPL driven
| programming is better served by an editor like emacs rather
| than vim. You want something where you can quickly write/edit
| code in one buffer and send it to the REPL with a couple of
| keystrokes. The lower the "cost" of this operation the better;
| it should be as easy as typing (as it becomes that frequent of
| an operation).
|
| To help you choose: Common Lisp and Clojure are the most
| practical. They are both general-purpose languages with a
| wealth of libraries available. Common Lisp, as the older
| language, has far more literature available including some of
| the best programming books ever written. Scheme is the most
| beautiful language and has one of the best textbooks ever made:
| _Structure and Interpretation of Computer Programs_ (SICP).
| Emacs Lisp is the most fun, practical but also the most quirky.
| Luckily, learning any Lisp will put you in a better position to
| learn any other Lisp.
|
| Emacs Lisp is fun because it presents the most exciting part of
| REPL based programming: hacking a live, running system. Most of
| the time you run a lisp instance just for the purpose of
| development, but if your program is working and doing
| something, then why not hack on it while it's running? There's
| no difference between running a REPL in a "development"
| instance and a live, production instance. When you hack on
| emacs you are doing just that: hacking a live, running
| instance. It's not often you get to use a tool to hack on the
| tool itself while it's running.
|
| Whatever you choose, good luck on your journey. I truly envy
| those who have yet to experience the beauty of Lisp
| programming. It changes you forever.
| blunte wrote:
| Nice reply. In my case, I'm already an effective amateur at
| Clojure, but I only use the repl for testing ideas and
| debugging.
|
| In the same way there are guides that teach TDD, I would
| enjoy seeing a RDD (repl-driven ...) tutorial.
| simongray wrote:
| In Clojure the "Rich Comment Blocks" are a very common way
| to use the REPL: https://betweentwoparens.com/rich-comment-
| blocks
| tincholio wrote:
| You could have a look at this video by Sean Corfield:
| https://www.youtube.com/watch?v=UFY2rd05W2g It gives you a
| good idea of what the REPL workflow is like. Sean will give
| a more in-depth talk about this next week, and it will be
| available in the London Clojurians channel afterwards, if
| you're interested.
| smashedtoatoms wrote:
| You can use Common Lisp with VSCode pretty easily now with
| the Alive plugin. It uses SWANK under the hood, but you don't
| NEED emacs anymore for Common Lisp. You can also use Calva
| with Clojure and vscode. The emacs requirement is slowly
| going away. You get all the perks of hacking live running
| systems, no emacs required.
| coldtea wrote:
| > _Why not just learn a Lisp yourself? You 'll get far more
| out of it than watching a video._
|
| Because one is a 30 minute endeavor and the other is a
| weeks/years endeavor.
| ashtonkem wrote:
| Having used Clojure and Common Lisp professionally, I think
| advocates for REPL driven programming universally overstate the
| utility of a first rate REPL.
|
| Yes, it's nicer than Python's. Yes, it's convenient. No, I never
| ended up doing my development in the REPL _first_. Why? Because
| editing mistakes in a REPL usually sucks, because a REPL is not a
| text editor.
|
| What I ended up using heavily was REPL to file integration, which
| gave me the ability to write a function normally, evaluate it in
| the attached REPL session, and then play around with it in the
| REPL. This is far short of the "REPL driven development" that's
| commonly discussed, and frankly something that's probably
| possible with the Python REPL if they wanted to.
|
| Editing data and functions in the REPL is a neat trick, but it's
| a double edged sword, because a REPL can crash, and it provides
| incredibly rudimentary support for diffing current state and
| migrating changes back to permanent files. I would never start
| with anything more than a trivial "how do I manipulate this list"
| in the REPL for that reason. Oh, and we had a lot of issues with
| REPLs getting into a bad state with Clojure due to multi methods
| and protocols; if you're doing your primary work in the REPL,
| then having to restart it due to it becoming unstable really
| sucks.
| xkeysc0re wrote:
| >What I ended up using heavily was REPL to file integration,
| which gave me the ability to write a function normally,
| evaluate it in the attached REPL session, and then play around
| with it in the REPL. This is far short of the "REPL driven
| development" that's commonly discussed, and frankly something
| that's probably possible with the Python REPL if they wanted
| to.
|
| I mean, yeah. You just import the Python module you're writing
| and you can call any function or inspect any object you want.
| iPython even has hot reload
| Folcon wrote:
| I'm personally of the opinion that this isn't the same thing
| and I'm saying this as someone who wants to do this in
| python.
|
| Unless you've figured it out? In which case please say,
| because I want to code python like this.
|
| Specifically I write code in my editor, hit a keystroke and
| all the code at the cursor gets sent to my running python and
| evaluated. The editor needs to be aware of python's indenting
| rules so it can do this, but once again if you've worked out
| a nice way to do this please say. Minimal keystrokes required
| would be great =)...
|
| I'm not really requiring that the evaluated output gets sent
| to the editor, though of course that would be ideal, because
| in the worst case I can side-by-side the editor and the
| terminal.
|
| From my perspective jupyter notebooks get kind of close, but
| I'm not coding in a file, so it's not quite ideal.
| hirple wrote:
| org-babel might work here. There are also repl keybindings
| in python mode in emacs.
| Izkata wrote:
| > Specifically I write code in my editor, hit a keystroke
| and all the code at the cursor gets sent to my running
| python and evaluated. The editor needs to be aware of
| python's indenting rules so it can do this, but once again
| if you've worked out a nice way to do this please say.
| Minimal keystrokes required would be great =)...
|
| ...I call this "copy/paste". And since I use X clipboard
| and a tiled window manager, it's only a couple keystrokes
| and no mouse. Highlight the code in the editor,
| "super+<direction>" to switch windows, "shift+insert" to
| paste.
| Folcon wrote:
| I don't quite agree, that might work for your specific
| workflow, but you can flub that sort of thing.
|
| There's a reason I'm specifying specific actions, I can
| easily see increasing the cognitive burden breaking the
| ease of that workflow. You can't tell me that putting
| your cursor to a code block and hitting a key is the same
| as selection, copy, swap to console, paste...
|
| I've certainly had situations where I've done what you're
| describing and then still taken the extra time to setup
| the workflow I've described above because it's worthwhile
| when you're trying to debug a knotty problem.
| petemc_ wrote:
| Not a keystroke and I'm probably being captain obvious, but
| I use pythons execfile for this.
| reddit_clone wrote:
| You don't have to type up stuff in repl literally. You can
| write regular functions in emacs and send pieces of code to the
| running repl.
|
| For adhoc testing code you can type on the repl buffer.
| blandflakes wrote:
| I 100% agree with you and another comment:
| https://news.ycombinator.com/item?id=25622990
|
| I spent years in Clojure and found that writing code in the
| REPL just... isn't fun. This has been true for every
| interactive system I've ever used (including... the shell).
|
| The shell as a REPL for a succinct language is nice for
| interactive workflows, but for trying to build less ephemeral
| pieces of code, the editor is the first class citizen I care
| about. Sending code to a REPL to be interacted with? Cool, and
| I _think_ this is what a lot of people mean when they say REPL-
| driven.
|
| I'd imagine convenience around that in a language or its
| tooling would be what convinces me to do more REPL-style
| things.
| koz_ wrote:
| I think what people mean when they say REPL-driven is to have
| a headless REPL that one never actually interacts with
| directly, but rather sends it snippets of code for evaluation
| directly from one's editor. Typing code into a shell prompt
| is excruciating by comparison.
|
| I think this is a huge barrier of entry to languages like
| Clojure. The syntax and dynamic nature of the language really
| lend themselves to an extremely interactive and productive
| workflow, but that is a hard thing to communicate. It seems
| most people think that when Clojure programmers refer to the
| joy of the REPL they imagine writing code into Python or
| Ruby's REPLs (e.g. https://repl.it/languages/python3), but
| that is about as far from what is meant as is possible. I
| mean, who would enjoy programming in an environment that
| deleted your work every time you finished a line of code?
| mikelevins wrote:
| As I said elsewhere, when I say "repl-driven programming", I do
| not mean "programming in a repl window", or "programming at a
| command shell". I'm talking about the runtime's read-eval-print
| loop, not the UI's repl window.
|
| When I'm working, there is little or no migrating of code from
| the repl buffer to a file because it's already all in a file. I
| almost always work in a file. I write snippets in a file, send
| them to the repl with a keystroke, and build up the world
| incrementally as I discover what it needs to be. As the
| contents of the file get larger and more complicated, I move
| things around and organize them. It's a conversation _with_ the
| repl, not an editing session _in_ the repl _window_.
|
| I don't consider any Clojure tools I know of to constitute a
| proper repl-driven environment, precisely because the language
| and runtime lack support for the kinds of programming and
| debugging that I've taken for granted for decades. If I can't
| inspect and edit and redefine _everything_ in the runtime, it
| 's not the full, proper set of tools.
|
| I've written a good bit of Clojure code, and I'll happily do it
| again if I need to do something that isn't convenient in, say,
| Common Lisp, but I consider it an acceptable alternative to
| things like Haskell and Scala and F# and Swift, not an
| attractive alternative to Lisp and Smalltalk.
|
| I do occasionally need to restart a Lisp environment because of
| some gnarly breakage I've committed, but it's pretty rare.
| Moreover, killing and restarting my favorite lisps takes about
| --wait, let me check--okay, a second and a half to kill a live
| app in staging and have it back up, fully-functioning.
| svnpenn wrote:
| If your code is in a file, and you're running the code from
| the file, that's not REPL. that's interpreting or compiling a
| file. REPL is opening interactive mode (if the language
| supports it) and entering "2+2".
|
| You calling anything else REPL is just wrong, misleading and
| confusing.
| 0x445442 wrote:
| I think most Lispers reckon REPL development the way it was
| described; analogous to editing code in a Smalltalk Browser
| and Doing/Inspecting it in a Smalltalk Workspace. WRT
| Smalltalk, you can Do/Inspect code in a Workspace and then
| create a class and copy it over but this typically isn't
| done that much.
| dragonwriter wrote:
| > If your code is in a file, and you're running the code
| from the file, that's not REPL. that's interpreting or
| compiling a file.
|
| It is not "interpreting or compiling a file" if the tool
| you are using to evaluate code in the file is sending
| isolated blocks of code to a REPL and not, well,
| interpreting or compiling the file.
|
| > REPL is opening interactive mode (if the language
| supports it) and entering "2+2".
|
| No, a Read-Eval-Print Loop (REPL) is not exclusively used
| by a user typing keystrokes at a terminal; like other
| terminal software, it is a candidate for being driven by
| other programs.
|
| > You calling anything else REPL is just wrong, misleading
| and confusing.
|
| You insisting a REPL isn't a REPL when the "Read" part is
| reading something other than keystrokes directly entered by
| a human user is just wrong, ignorant, and confused.
| mikelevins wrote:
| 'REPL is opening interactive mode (if the language supports
| it) and entering "2+2".'
|
| If I'm going to enter "(+ 2 2)" using my normal development
| tools, I'm probably going to start Emacs and tell it to
| start a SLIME session with one of the Common Lisps I use.
| When I do that, it'll create a repl buffer, because that's
| the way I have slime configured.
|
| I mean, I don't have to load slime-repl. I could just
| interact with the Lisp's read, eval, and print functions
| directly from an arbitrary buffer without involving the
| SLIME repl display at all. But, y'know, force of habit.
| Also, I do occasionally type something in SLIME's repl
| buffer, especially if I want to see some big wad of output
| and don't want it spewed into the middle of the expressions
| I'm working with.
|
| Most likely I won't type "(+ 2 2)" in the slime-repl
| buffer, though. I'm more likely to type it into a scratch
| buffer and send it to the Lisp by hitting C-x C-e. As soon
| as I start typing Lisp code, I know I'm probably going to
| want to add some more and maybe edit it and probably send
| it to the Lisp again. That's just more convenient if I have
| it sitting in a buffer in front of me, and not scrolling
| off the top of the repl buffer into infinity.
|
| So am I working in a repl? Lisp is running a loop waiting
| for input. I'm sending expressions to it. It's reading
| them, evaluating the s-expressions produced by READ, and
| then printing the resulting values to a stream I can look
| at. Sounds like a repl to me. It's what I've always
| understood a repl to mean, including when I'm building
| them.
|
| Does it count as working in a repl when I use a keystroke
| to send "(+ 2 2)" to the Lisp from the scratch buffer, or
| does it only count if I actually physically type the text
| in the buffer where the Lisp displays its prompt? What if
| there is no prompt? What if I start Lisp and SLIME but
| don't load the slime-repl extension? Does that mean that
| the loop that is reading, evaluating, and printing stops
| being a repl?
|
| I'll probably save my scratch buffer to a file at some
| point, if there starts to be enough text in it that I think
| I might forget some of it. Does it stop being an
| interaction with the repl the instant I save the buffer to
| a file? I can save the slime-repl buffer to a file, too;
| does it stop being a repl when I do?
|
| What about Lisp and Smalltalk environments where the read-
| eval-print loop doesn't display a prompt? Take a Smalltalk
| or INTERLISP worksheet, for example. Is it a repl if
| there's a loop reading, evaluating, and printing
| expressions, but no prompt? Does it stop being a repl if
| the incremental inputs and outputs get saved to a file?
| Does it count if it's not a text file, but the environment
| automatically saves the state of the worksheet to an image
| file that it automatically deserializes the next time you
| start the environment?
|
| I don't think I'll adopt your lexicon, but I am sort of
| curious where its boundaries are.
| dkersten wrote:
| Yeah, outside of simple use cases, I mainly interact with the
| clojure REPL by evaluating forms directly from my editor.
| It's about being able to incrementally build something while
| having it running and updated as you go.
|
| I also tend to often just reload changed namespaces and
| resetting the (component/mount/integrant based) application
| too. Or letting shadow-cljs live reload my clojurescript. But
| having the REPL directly accessible is still super useful.
| jmchuster wrote:
| This is what I use as my canonical example of REPL-driven
| development. https://vimeo.com/230220635
|
| The main thing that stands out to me is playing with small
| snippets of code in real-time as you're writing it. Contrast
| that with writing a class and methods, then writing unit tests,
| then running them. There might as much as ten minutes between
| the time you start writing your code and when you run any
| portion of it, and always running large chunks of changes.
| 0x445442 wrote:
| Having never developed code professionally in Lisp, my
| question would be; does the REPL work flow replace test code?
| Because if the REPL is being used as a replacement for tests,
| then I can see future readers having a harder time groking
| the code without the benefit of test driver code to analyze.
| mikelevins wrote:
| I'd say it's more like tests grow naturally out of repl
| interactions.
|
| Test-driven development as a discipline emerged from the
| Smalltalk community, and I don't think that's an accident.
| I think it's a formalization of the way that Smalltalk and
| Lisp programmers naturally tended to work.
|
| I generally start on something by making a naive model of
| what I'm trying to accomplish and interrogating it. In
| successive iterations, I modify the model and the
| interrogations in the direction of what I need it to be,
| discovering details along the way.
|
| I'd say that a majority of my actual activity is testing.
| That's one reason I prefer to interact with a repl from a
| file: because I want a record of my interactions. I want to
| be able to do them again and again.
|
| The difference between that record and formal tests is a
| simple matter of copying the trail of my interactions into
| a test framework, and that, too, is how I normally work:
| make model; test it with interactions; keep the
| interactions around for later.
|
| As I make progress, the models turn into data structures,
| and the interactions turn into functions and tests.
| reddit_clone wrote:
| Nothing is lost. You would convert the ad-hoc test code
| from the repl into actual unit tests in your source code.
| With the added good feeling they have already been executed
| successfully at least once.
| chris_j wrote:
| > What I ended up using heavily was REPL to file integration,
| which gave me the ability to write a function normally,
| evaluate it in the attached REPL session, and then play around
| with it in the REPL
|
| When I do REPL driven development in Clojure, that's exactly
| what I do and that's what other folks that I know mean by REPL
| driven development (in Clojure): define types, functions and
| variables in a source file, often in a (comment) form, eval
| them one by one, and copy the code out of the (comment) form
| when it's stable enough. I wouldn't type code directly into the
| REPL; that's not a pleasant experience in Clojure - but might
| be pleasant in Common Lisp or Smalltalk for all I know. The
| process that I and others use in Clojure most definitely
| differs from the process that the OP describes and I lack
| enough familiarity with Common Lisp to know if the process is
| more pleasant in that language. I assume it is and I must make
| time to learn Common Lisp properly some day.
| TheAnswerMan wrote:
| You don't even need the "REPL" buffer to do REPL style
| development. You can just use the "eval" keybinds of your
| editor to eval the source code in the files.
|
| For any test/scratch code, just create a scratch file outside
| your project and write the experiments there, eval'ing as you
| type. That file could even be an "official" unit test you
| include in the project.
| tlarkworthy wrote:
| I think the best modern example is ObservableHQ [1]. I took a
| screen shot yesterday when I found myself interleaving runtime,
| TDD, IDE, debugger and partial recompilation in one window. It
| blew my mind.
|
| I have never experienced such a productive programming
| environment.
| https://twitter.com/tomlarkworthy/status/1345321532650905601...
|
| For there I can change variable at runtime. Change the
| implementation and have the tests auto run. Correct the tests,
| set a breakpoint in the test or implementation with "debugger;".
|
| I think it might be more productive than smalltalk because of the
| spreadsheet-like reactive recomputation. Plus it supports real
| markup as inline documentation.
|
| [1] https://observablehq.com/@tomlarkworthy/rate-estimation
| mikelevins wrote:
| Thanks for pointing it out. The site went on my reading list.
| oumua_don17 wrote:
| Writing a ray tracer in Common Lisp is an excellent video that
| helps one understand how live editing aka REPL driven programming
| works with a Lisp such as CL that supports it from the ground up.
|
| [1] https://www.youtube.com/watch?v=N1oMRw04W3E
|
| edit: fix typo
| xg15 wrote:
| Question by someone who never used interacted development before:
| How do you save the results of your work? How do you ensure the
| state of your program is what you think it is.
|
| E.g.: Imagine you're in a breakloop as described in the article.
| You find that local variable X is 5 when it really should be 4,
| so you quickly set it to 4. You find function foo() is not
| defined, so you define it. You continue and your program works.
| Great!
|
| At the end of the day, you quit your environment and shut down.
| How do you ensure your interactive work is not lost and the
| environment is still what you expect it to be when you start
| again the next day. How would you compile such a program?
|
| Is there some command that dumps the whole environment to a file
| as source code? Would you save your REPL history? Would you
| manually copy/paste relevant bits to s code file?
|
| Also, if significant parts of the source code are written inside
| the REPL, wouldn't the lack of modern IDE features be a hassle?
| No syntax highlighting, no code completion, no code inspections
| etc. Or are there tools that offer those?
| Jtsummers wrote:
| You can edit the source file and send it to the listener.
|
| Suppose I'm in my REPL and I do this: > (foo
| 'bar)
|
| I get dropped into the debugger because _foo_ doesn 't exist.
| So I switch over to my .lisp file in another buffer and I type:
| (defun foo (symbol) (format t "Baz ~A~%" symbol))
| ;; this will print "Baz BAR" in the example
|
| With the cursor _anywhere_ within that function definition I
| type C-c C-c, go to the debugger and restart it. It works now.
|
| My source file and my Lisp image are both synchronized.
|
| In the case of changing a variable. Say I'm somewhere deep in
| some code and this tries to run: ;; in context
| x = 0 (/ y x)
|
| I'm dropped into the debugger. I _know_ x was supposed to have
| a minimum of 1. I can do a couple things:
|
| 1. Examine the back trace in the debugger to see if I can
| locate where x got an incorrect value (maybe it was the line
| above, maybe 10 functions earlier). I make a note of what to
| fix, maybe I fix it now.
|
| 2. Change x to its proper minimum value and resume.
|
| If I was able to trace how x got a wrong value, it's fixed
| already. If not, I'll have to explore more. But I don't have to
| restart _way_ earlier, I can just restart at this point
| (assuming nothing else was broken by x having the wrong value
| here) and still get the result of my program (assuming x being
| wrong didn 't break a lot of other things). Or I can quit back
| to the REPL and start exploring the problem.
| nlitened wrote:
| The way I do it with Clojure, you never really write any code
| in REPL prompt directly. You write it in your source code file,
| then send a piece of code to the REPL to update the "in-memory"
| state of your running program.
|
| So any modifications you make stay in your files, just like you
| would normally do with any other language. But you write your
| program while it's running, and you grow/change it piece-by-
| piece, until it's done (by that time, your source code is
| exactly your finished program).
|
| If you want to re-start your REPL session, you just run your
| current file in the REPL as a whole, and maybe run some "Rich
| comments" (from the same file) to set up a limited test
| environment (to isolate the piece of program you're working
| on).
|
| This way, you make use of syntax highlighting and all the IDE
| features.
|
| Edit: I've re-read your example with breakloop -- I don't
| really have experience with this style of REPLing. I agree that
| it raises a lot of questions, but thankfully functional nature
| of Clojure discourages this kind of programming.
| proverbialbunny wrote:
| It depends what kind of environment you're using. Eg, notebooks
| like Jupyter are a kind of REPL environment, which is how data
| scientists typically write code. You prototype in a cell that
| is the source code that gets saved. If you've finalized the
| function or piece you're working on, you go to another cell.
|
| The reason data scientists do it this way is load times are
| gruesome for data science work. Sometimes it takes days to
| process something. Imagine every time you change a line of code
| having to wait days to see if there is a bug in code or what
| the output is. Instead we run it in an environment that loads
| it once and keeps it in ram (a REPL), so when working on code
| below what is already loaded there are no load times.
|
| There are other kinds of environments though. Sometimes video
| game devs develop on a REPL where the game is running, they
| pause it, update some code, and then go back into the game with
| the updates automatically put in. No load times.
| adamkl wrote:
| For anyone who would like to see what REPL-driven programming
| looks like in practice, take a look at this video here:
|
| https://vimeo.com/230220635
|
| The author uses the Clojure REPL to walk through the process of
| developing some non-trivial functionality (calling an API and
| parsing the results).
|
| It's a good intro to what it looks like to interactively build a
| program while it's running.
|
| Personally, I've found that having such a tight feedback loop
| makes development a lot more enjoyable.
| irjoe wrote:
| Interesting. I do the same thing in Elixir where I'll attach an
| iex session to a Phoenix application so I can interrogate
| modules and APIs as I'm building them out.
|
| I'm slightly disappointed that it's already something I do day
| to day. I had hoped that the power of the REPL wasn't
| overstated.
| adamkl wrote:
| I think just because someone is able to do something similar
| in another language doesn't mean that the power of a fully
| integrated REPL is overstated.
|
| Most developers are using languages where this sort of thing
| isn't possible, and for them, experiencing a REPL driven
| development flow can be an eye opening experience (even if
| it's just to add it to their tool box along side more common
| approaches like attaching debuggers and using TDD to shorten
| the development feedback loop).
|
| I don't know enough about Elixir to understand how your
| approach is the same/different than using something like a
| REPL with Clojure, but I did come across a pretty interesting
| discussion on the topic:
|
| https://elixirforum.com/t/what-do-you-all-think-of-
| clojures-...
|
| TL;DR - you can accomplish something similar with Elixir, but
| the underlying technical details are different.
| rozap wrote:
| It's nice to be able to do this in production for debugging.
| It's super powerful and one of the my favorite features of
| erlang.
| phissenschaft wrote:
| My concept of a "REPL" is mostly defined by emacs. You would have
| a buffer with a code file, with an active jupyter kernel with the
| correct dependencies loaded in it. Then one would send any active
| region with `C-c C-c` and get timely feedback.
|
| With this mode https://github.com/nnicandro/emacs-jupyter one can
| connect to a jupyter kernel running locally or remote (would
| mostly prefer SSH port forwarding or kubectl port-forward the
| remote jupyter server). It makes life so much easier to interact
| with cloud environment (e.g. spark).
| crabmusket wrote:
| This reminds me of Unison's "codebase manager":
| https://www.unisonweb.org/docs/tour#-the-big-technical-idea
|
| It's not exactly a repl, but it shares similarities to the idea
| of interacting with your source code itself via software, not
| just by typing bytes into a file.
| globular-toast wrote:
| As someone who has used CL and Clojure to write non-trivial code
| using REPL driven programming it makes me mad that the word REPL
| has been hijacked to mean "interactive shell".
|
| I'm thankful for this article shedding light on what it really
| is. It's sad that most programmers haven't experienced.
| lispm wrote:
| The examples from Mikel are just not possible in a default
| Clojure setup. It does not have break loops and it does not
| have a dynamic object system (by design).
| tluyben2 wrote:
| Ah that answers my question I guess. Thanks
| globular-toast wrote:
| Yeah, true. I would still consider Clojure a "proper" REPL
| driven language, though.
| mikelevins wrote:
| I do not. I have used it to write quite a bit of code, and
| I generally like it as a language, but I don't consider it
| a reasonable substitute for Common Lisp or Smalltalk.
| Factor, on the other hand, is--as far as I know.
| tluyben2 wrote:
| I have some experience with clojure but besides using the repl
| as 'interactive shell' I never tried it further (for some
| reason I always thought it was a crippled lisp even though I
| did some fair amount of work in it); does it offer the full
| experience? I should try it again as that's something that's
| actually used in real life (and has a good client side cljs
| experience).
| mikelevins wrote:
| It does not offer the full experience. It has no breakloop
| and no ability to browse and edit the live running
| environment. It has no ability to rummage around inside the
| dynamic environment of a suspended function call, much less
| to redefine the suspended function or the types of its
| parameters, nor to restart the suspended call. Indeed, the
| JVM makes some of that stuff really inconvenient to do.
|
| You cannot do _everything_ from the Clojure repl in the way
| you can from a Common Lisp repl or from a Smalltalk
| worksheet.
|
| I don't know that the Clojure language design _forbids_ it;
| you might, for example, implement Clojure on top of a Lisp or
| Smalltalk environment and hook up their tools to Clojure
| through its interop. That might work, and ruricolist has been
| working on such an implementation called Cloture:
|
| https://github.com/ruricolist/cloture
|
| But existing Clojure implementations don't have the full set
| of tools.
| skybrian wrote:
| It seems like the closest thing to this in widespread use is a
| SQL database. If you want to change a table, you can run an ALTER
| TABLE command.
|
| But of course you don't want to do this in the live, production
| database unless you're a DBA who is doing an upgrade manually for
| some (probably bad) reason.
|
| So, you can try out your ALTER TABLE commands in a scratch
| database, but you'll need to save them to a migration script, and
| test the migration.
|
| It seems like a weakness of this sort of live updating? Sure, you
| can modify your own running instance, but upgrading the
| production instance(s) will often still be a lot of work.
| Migrating a production database schema tends to be a tricky
| thing.
| irjoe wrote:
| Is there a simple way to get code I write in a lisp REPL back
| into my editor? That's the part missing for me and why I usually
| only use interactive shells (REPL or otherwise) for testing APIs
| or small pieces of code.
|
| I can't imagine writing a program in its entirety in a REPL.
| gumby wrote:
| The standard technique is to run the loop inside the editor.
| This technique dates back to the 1970s.
| tarboreus wrote:
| You write it in the editor and send statements to the REPL.
| You're not just sitting looking at a command line. The REPL is
| a conversation with running state, but the conversation doesn't
| have to take place only through a single blinking terminal
| window. In proper REPL-driven development, the editor is fully
| integrated with the running session, and you can evaluate
| either in an attached terminal session or through your editor.
| Or by attaching whatever other tools to the running session.
| mikelevins wrote:
| Yes, exactly. It's perhaps my fault for not making this more
| explicit in the essay, but the "repl" in "repl-driven
| programming" does not mean the repl _window_.
| O_H_E wrote:
| Yeah, I suggest you expand upon that, or perhaps another
| article showing that "conversation". Unfortunately, some
| people - including me - have only used a repl in "one-
| direction" and have to copy stuff back and forth.
| mikelevins wrote:
| That's a good suggestion. Walking through a set of
| interactions is a solid idea.
|
| For what it's worth, there are some videos around of
| people actually doing it with Lisp and Smalltalk systems,
| and pjmlp already posted a pile of them elsewhere in this
| thread.
|
| I can add a few more:
|
| Kalman Reti walking through some interactions with a
| Symbolics LispM repl:
|
| https://www.youtube.com/watch?v=o4-YnLpLgtk
|
| Brian Mastenbrook demonstrating Interlisp's SEDIT
| structure editor in the Xerox Lisp environment:
|
| https://www.youtube.com/watch?v=2qsmF8HHskg
|
| Rainer Joswig (lispm here on HN) showing us a little bit
| of repl and Zmacs interaction on a Symbolics Lisp
| Machine:
|
| https://www.youtube.com/watch?v=LIGt5OwkoMA&list=PLN1hNlV
| qKB...
|
| Rainer again, showing some simple interactions with
| Macintosh Common Lisp, which was my daily driver for
| years:
|
| https://www.youtube.com/watch?v=GKG8cJl70mo
|
| Ruby programmer Avdi Grimm shows some things that he
| found cool about Pharo Smalltalk:
|
| https://www.youtube.com/watch?v=HOuZyOKa91o
|
| Dan Ingalls (one of the original authors of Smalltalk) in
| a 2017 demo of Smalltalk 76:
|
| https://www.youtube.com/watch?v=NqKyHEJe9_w
|
| There are some other things I'd like to find for lists
| like this, but haven't been able to. In particular, a
| good demo of Apple's SK8 would be great.
|
| If you can imagine a full-color Hypercard that could
| crack open and reprogram absolutely everything on the
| screen, including the machine code that drew the window
| system's widgets, all in a repl while the code was live;
| in which you could grab an arbitrary widget and drop it
| on the repl window to get a live variable reference to
| the widget, and then inspect it, operate on it, and
| reprogram it, again, while everything continued to run;
| in which you could build new window-system widgets by
| snapping together shapes and telling them to become
| widgets; in which you were not limited to HyperTalk for
| coding and text strings for data, but had a full Common
| Lisp at your disposal plus a Minsky-style frame system
| for representing data and knowledge, then you have some
| idea of what SK8 was like.
| lebuffon wrote:
| Nobody has mentioned low-level programming in a REPL. Not as
| common to be sure. Forth works this way and even has REPL
| Assembly Language. It's been there for over 40 years.
|
| Testing Forth and ASM code snippets in the REPL before committing
| to them helps eliminate those nasty assumptions about what
| "should" work.
| mikelevins wrote:
| Agreed, FORTH is repl-driven in the sense I mean. The main
| difference from Lisp and Smalltalk systems is that FORTH
| environments are, generally speaking, more spartan.
|
| In the late 1980s I had a group of friends at Apple that
| included Smalltalk, Lisp, and FORTH programmers. We certainly
| found plenty of things to admire and attempt to steal from one
| another, and everyone accepted the basic goodness of building
| systems by engaging in conversation with them as they run.
|
| Lisp and Smalltalk cross-pollenated each other more than each
| did with FORTH, but maybe Slava Pestov's Factor is a glimpse of
| what you get if FORTH is more in the mix.
| 0x445442 wrote:
| Yeah, Factor's environment looks really similar to something
| like Smalltalk. The thing with Forth is that it looks really
| daunting to perform higher level tasks that I'd be interested
| in performing on a day to day basis.
| mikelevins wrote:
| It can definitely be done, but historically, that's not
| really been its sweet spot.
| tluyben2 wrote:
| I think it would help as well to list languages that have various
| levels of 'real repl' implementations. Wonder what modern
| (systems you can 'make money with') there are that support this.
| I know common lisp + smalltalk and worked with both and really
| liked them for these reasons. I miss this functionality all the
| time as it was far more efficient (to me!) than modern debuggers.
| rightbyte wrote:
| Isn't Python and Matlab a 'real' REPL? They are money makers.
| mettamage wrote:
| The article specifically stated that Python does not have a
| real Read-Eval-Print-Loop mechanism like Lisp or Smalltalk.
| For example, the Python REPL doesn't go into a breakloop
| mechanism for undefined functions.
| mikelevins wrote:
| It's not about whether they make money. It's about whether
| you can do absolutely everything needed to build your program
| interactively by talking to it while it's while it's running.
| coldtea wrote:
| The article describes specific capabilities (full
| program/system dynamic redefinition) afforded by a real or
| full REPL.
|
| Python doesn't have that, and it's mentioned explicitly as
| not having that.
|
| It's not about "existing in the real world" or "real enough
| to make money with".
| mvc wrote:
| Definitions matter. A repl is a read, eval, print, loop.
|
| That means the code you enter at the prompt is converted from a
| String into literal data that can be evaluated (read). The data
| is then evaluated to produce a result (eval), and the result is
| then printed (print).
|
| If that's not what your REPL is doing then it's not a repl.
|
| That is not what's going on in e.g. python or ruby "repls". There
| is no "read" step here converting the text of the program to
| data. The program text is simply passed to an "eval" function
| that produces the result directly.
| TazeTSchnitzel wrote:
| This seems extremely pedantic. Python and Ruby will "read" the
| string inside the "eval" function.
| mikelevins wrote:
| Other people are making the same point I'm about to make, but
| I'm going to try to clarify it anyway because, y'know,
| besides being a programmer, I'm also a technical writer, and
| I just have to scratch that itch.
|
| Common Lisp source code (and the source code of its immediate
| ancestors) is not made of text strings. It's made of
| S-expressions, which are made of cons cells, symbols,
| numbers, and so on.
|
| A text file of "Lisp source code" does not actually contain
| Lisp source code. It contains a text-based serialization of
| Lisp source code. Other serializations are possible (and
| there are things you can do in a Common Lisp repl to see some
| of them).
|
| The "read" in "read-eval-print" means "deserialize the text
| into the source data that it's meant to represent".
|
| This point is not trivial pedantry because the full power of
| the Lisp language is available to the read process, and can
| be brought to bear on how reading is done and what happens
| when you do it. Compilers for other languages certainly do
| read text strings and convert them into tree structures and
| so forth, but the difference is that those data structures
| are private to the compiler; the data structures that Lisp
| reads into are standard parts of Lisp's public API, as are
| the read function, the compile function, the eval function,
| the print function, and so on. It's all on the table for you
| to work with.
|
| The same is true of the disposition of the s-expressions
| produced by the read process; you have an opportunity to
| bring the whole of the Lisp language to bear on those
| s-expressions before they are ever passed to (compile or)
| eval. Then, once again, what eval produces is S-expressions,
| and those, not strings, are passed to the print function. You
| once again have the opportunity to intervene in the process
| that produces the text serialization.
|
| It so happens that I've spent the past six months working on
| an AI machine-control system written in Common Lisp, and
| every one of these capabilities was an important part of the
| work we were doing.
| lispm wrote:
| In Lisp it makes a difference, because source code is data
| (other than text) and one can also compute source code in the
| REPL.
|
| For example we can write a macro in Lisp and play around with
| it giving it code as data and see the result as code as data.
| CL-USER 1 > (defmacro while (condition &body body)
| `(tagbody start (if (not
| ,condition) (go end)) ,@body
| (go start) end))
| WHILE CL-USER 2 > (setf a 1) 1
| CL-USER 3 > '(while (< a 4) (print a) (incf a))
| (WHILE (< A 4) (PRINT A) (INCF A)) CL-USER 4 >
| (macroexpand-1 *) (TAGBODY START (IF (NOT (< A 4))
| (GO END)) (PRINT A) (INCF A) (GO START) END) T
| CL-USER 5 > (pprint *) (TAGBODY START
| (IF (NOT (< A 4)) (GO END)) (PRINT A)
| (INCF A) (GO START) END)
| CL-USER 6 > (eval ***) 1 2 3
| NIL
| kitd wrote:
| All very nice, but in Python/Ruby/etc, the source code
| isn't data. So your point is only relevant to Lisp, not
| REPLs generally.
| lispm wrote:
| That's why it is not a REPL (Read Eval Print Loop), but a
| ReadString, Parse, Compile, Execute, Loop.
| [deleted]
| boxmonster wrote:
| I've been doing debugger driven development my entire adult life
| and consider it a bad habit. I'm much better off and happier when
| I plan out and compile in my head. Then I get a good feeling when
| it compiles and runs the first time. If not, then I need to think
| about it some more, not sit in the debugger trying things out.
| mikewarot wrote:
| I had no idea this was possible now, and to find it was
| commonplace for decades makes it even more amazing, and worrying
| what else I've missed. Thanks for sharing this here!
| pjmlp wrote:
| Be prepared to be amazed with what Xerox PARC and others were
| doing, while Bell Labs was busy pushing for UNIX.
|
| "Eric Bier Demonstrates Cedar"
|
| https://www.youtube.com/watch?v=z_dt7NG38V4
|
| "Emulating a Xerox Star (8010) Information System Running the
| Xerox Development Environment (XDE) 5.0"
|
| https://www.youtube.com/watch?v=HP4hRUEIuxo
|
| "Documents as User Interfaces Video Demo"
|
| https://www.youtube.com/watch?v=0-_zVkrWCOk
|
| "SYMBOLICS S-PACKAGES 3D GRAPHICS AND ANIMATION DEMO"
|
| https://www.youtube.com/watch?v=gV5obrYaogU
|
| "Alto System Project: Dan Ingalls demonstrates Smalltalk"
|
| https://www.youtube.com/watch?v=uknEhXyZgsg
|
| "Action!, the worlds first dynamic interface builder - 1988"
| (Interface Builder percursor, written in Lisp)
|
| https://vimeo.com/62618532
|
| "The Interlisp programming environment"
|
| http://larry.masinter.net/interlisp-ieee.pdf
|
| And the cherry, how Lucid used Lisp ideas for their Energize
| C++ IDE, including an image based format for AST storage
|
| https://www.youtube.com/watch?v=pQQTScuApWk
|
| https://www.dreamsongs.com/Cadillac.html
| hcarvalhoalves wrote:
| Watching the Symbolics demo nowadays, it's amazing what they
| had back then.
| vblxt wrote:
| These lock-in IDE environments, while impressive, thankfully
| never got really popular.
|
| I find superior languages like Lisp or Standard ML most
| enjoyable in text files.
| pjmlp wrote:
| Look better around you.
|
| - macOS, Windows, Android, ChromeOS
|
| - InteliJ, Visual Studio, XCode/Playgrounds
| ncfausti wrote:
| For people who use Clojure (or another REPL-capable language),
| would you say that this is the main reason why you use it?
|
| Is it common to write Clojure or other REPL-capable languages in
| a more "traditional" manner (like how one would write say, Java
| or C)?
| tincholio wrote:
| I'm not sure I'd say the REPL is "the main" reason I use
| Clojure (there are many reasons, really), but it is an
| important part of the experience.
|
| I don't know of anyone who is experienced in Clojure and works
| in a "write-compile-test" way like you'd do in C or Java. While
| it's certainly feasible, it's not how you're supposed to do it.
| psykotic wrote:
| He's right to emphasize the properties of the systems that
| support interactive development with a long-running image. In
| Common Lisp, the difference between defvar and defparameter is a
| simple example. Traditional Smalltalk systems only supported
| image-based development. But I always preferred the moderate
| approach exemplified by most Lisp systems where the source code
| isn't overly entangled with the image state.
|
| As a long-time but now lapsed Lisper, I never understood some
| people's elevation of the REPL. A command-line REPL is a poor
| man's development environment. If you're doing interactive
| development in Lisp, you'll be far more productive if you use a
| normal buffer with eval-defun, eval-buffer and friends. When
| debugging, you'll primarily want auto-updating watch expressions
| and object inspectors. And when you do have good use for a REPL,
| you'll still want it to exist within a proper buffer with
| persistent history, inline object inspection, etc, instead of the
| low-effort rlwrap terminal experience which usually passes for a
| REPL.
|
| All of this holds doubly true for Smalltalk environments.
| mumblemumble wrote:
| Why not all of it?
|
| Long ago, I was doing Objective-C development with an embedded
| F-script REPL, hot code swapping, and XCode's built-in
| debugger. It was very nearly as nice as Smalltalk in many ways,
| only without the whole IBD situation.
| moocowtruck wrote:
| that doesn't sound nearly as nice as smalltalk
| lispm wrote:
| The mindset is slightly different. From a buffer one can
| interact with the system and it may show things inline.
|
| The REPL, often called a 'Listener' in Lisp is an explicit
| dialog with the system, where the dialog is visible and parts
| of the dialog can be reused and inspected. Also the dialog can
| be suspended temporarily and we interact with the running code
| itself in break loops until we resume the original dialog in
| some way.
|
| This usually is the horror for people wanting predictable code
| - where in an interactive Lisp, one can change code
| interactively at runtime.
| mikelevins wrote:
| You misunderstand me. By "repl-drive programming" I do not mean
| programming that is fixated on the "command-line repl". On the
| contrary, what I'm talking about has much more in common with
| what you describe as "far more productive". It's not about a
| particular shell or window or buffer; it's about a runtime
| environment that is designed to support writing software by
| building and changing it as it runs.
|
| By "repl", I do not mean the window or the buffer or the shell
| program. I mean the loop of: read an expression, evaluate it,
| and present the results, in the context of a runtime that is
| designed to comprehensively support it.
|
| Moreover, you can have all of the tools that you listed and
| still not be working with a properly-designed repl-driven
| environment. For example, I built a 3D interactive environment
| on the JVM that worked quite well--it launched into the 3D
| environment ns no more than a second or two, and could build
| whole procedurally-generated scenes in a few seconds. It
| supported networked multiuser interactions. I could start it
| from a repl and dynamically alter scenes and objects and their
| behavior in the environment by talking to them.
|
| It still wasn't a proper repl-driven environment because the
| underlying runtime could not correctly handle dynamic
| redefinition of classes and methods. That meant that if I
| decided that I needed to change a representation or something,
| I had to kill the environment and rebuild it. It meant that
| there was always a gratuitous barrier that I might run into at
| any moment. It meant that some abitrary set of things I was
| working on was always on the other side of that barrier.
|
| Contrast that to working with, for example, SK8, where I could
| redefine absolutely everything in the environment (including,
| for example, the system-level procedures used to draw window
| frames) without ever restarting the code that was under
| development.
___________________________________________________________________
(page generated 2021-01-04 23:01 UTC)