1. 25
  1.  

  2. 9

    https link seems to be borked.

    http://mikelevins.github.io/posts/2020-12-18-repl-driven/ works fine.

    1. 6

      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.

      1. 3

        If you want to write a system big enough to need “engineering” and professional maintenance, then I agree repl is flavour at best. If you want to be able to get more out of your computer without having to become a “programmer” it is a valuable strategy.

        1. 1

          “you found the problem, [and you can now write regression tests]”. Of course one doesn’t exclude the other. Better yet, if a unit test is failing, ask your test framework to pop up the interactive debugger on error, use it to find the problem. It’s just faster than the usual cycle. Hard things are also made simpler.

          1. 1

            It is a debugging and exploratory tool. As you are programming you can quickly test different hypotheses. Writing a test and scaffolding takes way more time.

          2. 5

            Great write-up, I had no idea the REPL of lisp/smalltalk was so powerful. I need to get around to learning clojure.

            I think the elixir* REPL fits the bill for the most part - if I start up one iex instance and connect to it from another node I can define modules/functions and they show up everywhere. And for hot-fixing in production one can connect to a running erlang/elixir node and fix modules/functions on the REPL live, and as long as the node doesn’t get restarted the fix will be there.

            * erlang doesn’t quite fit the bill since one can’t define modules/functions on the REPL, you have to compile them from the REPL.

            1. 3

              Does Clojure actually have these breakloops though? I think I’ve seen some libraries that allow doing parts of it (restarts), but isn’t the default a stacktrace and “back to the prompt”?

              1. 1

                Well, prompt being the Clojure repl, but you’re correct that the breakloop isn’t implemented, as far as I got in the language. You can must implement the new function and re-execute, so you lose all of the context previous to the break. I think with all of the customizability of what happens when a stack trace happens, it’s possibly possible.

                I THINK the expected use with Clojure is to try to keep functions so small and side effect free that they are easy to iterate on in a vacuum. Smalltalk and CL have not doubled down on functional and software transactional memory like Clojure has. That makes this a little more nuanced than “has/doesn’t have a feature”.

                1. 1

                  You’re correct. Interactivity and REPL affordances are areas where Clojure–otherwise an advancement over earlier Lisps–really suffers compared to, for instance, Common Lisp. You don’t have restarts, there is a lot you can’t do from the REPL, and it’s easy to get a REPL into a broken state that can’t be fixed without either a full process restart or using something like Stuart Sierra’s Component to force a full reload of your project (unless you know a ton about both the JVM and the internals of the Clojure compiler). You also can’t take a snapshot of a running image and start it back up later, as you can with other Lisps (and I believe Smalltalk). (This can be useful for creating significant applications that start up very quickly; not coincidentally, Clojure apps start up notoriously slowly.)

              2. 5

                I guess the most REPL-y experience most programmers have is bash… it even has two namespaces a la common lisp (./myfile and cat myfile)

                1. 2

                  It also has a persisted objects system, a.k.a. files :)

                2. 4

                  The author goes into some more detail in this post: https://mikelevins.github.io/2020/02/03/programming-as-teaching.html

                  I think it would be interesting to watch a recording of someone building something small but not completely trivial using this methodology. Reading about it, I’m a bit worried that this practice would encourage the kind of non-reproducibility that I think IDEs like R-studio do (and where do you keep all the code you’re writing?)

                  I think it ties in to Bret Victor and others’ ideas about learnable programming too: that it is really useful to be able to see and play with the running program and its data.

                  Finally, I think reactive notebooks, as pioneered by https://observablehq.com/, give some of the benefits that the author is interested in. There aren’t “breakloop” REPLs, but that’s because a functional reactive style is encouraged where it would be more natural to just edit the definitions in their places and to perhaps add some widgets for displaying or modifying values.

                  1. 2

                    I haven’t used observable notebooks, but I often grab a jupyter notebook before a repl when working with python. A big pain point in repls for me is the editing ergonomics. It’s much easier to edit a few different, multiline chunks of code at once in a notebook. Dropping into a break loop would make a big difference, but notebooks are still very convenient for playing with code. Swift’s playground and SQL IDEs are also very nice in that regard.

                    1. 1

                      For python, bpython is a neat drop-in replacement for the REPL. It may have not all the bells and whistles of a Lisp/Smalltalk REPL but it is on par with what utop is for Ocaml.

                      I don’t like Jupyter notebooks and Matlab-ish IDE (RStudio, Spyder, etc.) due to the amount of hidden state that bite me back every time.

                      1. 1

                        Observable notebooks like ObservableHQ and Pluto.jl are good because they try to remove all hidden state.

                        They’re not perfect at that, but if you avoid mutating values they work pretty well.

                        1. 1

                          That looks really nice! I’ll give it a shot. However, I would probably want to disable the pastebin upload. I would hate to fat finger and upload some secret keys to pastebin. @_@

                          I don’t like Jupyter notebooks and Matlab-ish IDE (RStudio, Spyder, etc.) due to the amount of hidden state that bite me back every time.

                          Yeah, that’s the trouble with Jupyter notebooks. It’s nice to write code non-linearly, but then you have to be aware of the dependencies between chunks of code. Observable notebooks seem nicer in that regard.

                    2. 3

                      It seems neat but not actually very useful. I can see what’s in scope by looking at the code, and I know the shape of the data from the types.

                      I think I need to see a video or something, as cmcaine mentioned.

                      1. 2

                        When I read this article, I immediately think of Forth. In Forth, you can have multiple “tasks” (somewhat similar to threads or fibers in modern languages). One of these tasks is the operator task, an interactive interpreter/compiler. It is possible to change how numbers are parsed and formatted, and many other aspects of how the language behaves. Just about everything is done through this interface.

                        Interactive development has been a part of the language since day one (~1969) and can be supported on even the tiniest of targets. There are no external libraries or toolchains that need to be added- it has always been there, and numerous assumptions have been baked into the language. Non-interactive Forth systems are the exception rather than the norm.

                        Unfortunately, I don’t have much experience in Lisp, so I am not able to compare it to Forth. I would be interested in hearing from someone who has experience with both languages to see how they compare.

                        1. 1

                          Python has the ability to reload code inside of the repl and/or debugger.

                          To use the IPython shell as your debugger, see https://github.com/gotcha/ipdb

                          1. 1

                            You’ll quickly run into issues in Python when trying to redefine classes and functions.

                          2. 1

                            I think Julia (especially with Revise.jl) probably comes close to this kind of ability for a newer system.

                            1. 3

                              I really like Julia and Revise, but it can only do some of the things in the article. Here are their points:

                              1. There are no artificial limitations on what the repl can do
                              • Julia is pretty good here, but see below, and also you have to tell Julia how many threads to use at startup and cannot change that from the REPL.
                              1. Some kinds of errors drop you into a “breakloop” repl that can see the whole suspended call stack and edit all live data
                              • Julia doesn’t do this by default, you can use the debugger but it’s not perfect
                              • Debugger.jl will run your code (slowly) in an interpreter and can let you inspect the call stack, and gives you a REPL that lets you evaluate arbitrary code, including editing local variables. Unfortunately the debugger is itself a bit buggy still
                              • Infiltrator.jl can put you in a REPL from a breakpoint in your program, but you have to specify in advance where it will be and you can’t edit local variables
                              1. You can redefine types and if you do existing values can be converted to the new type
                              • You cannot redefine a data type in Julia, they are constants. You could define a type with a different name and define conversion methods, but it’s not really the same
                              • There’s no public mechanism for removing defined methods (though Revise can do this) and no mechanism for undefining any names
                              1. You can develop a program by defining a top-level function and working through successive “breakloops”
                              • You can’t really do this in Julia