1. 10
  1.  

  2. 3

    Immutability. (…) This means you’ll tend to program with values, not side-effects. As such, programming languages which make it practical to program with immutable data structures are more REPL-friendly.

    Top-level definitions. Working at the REPL consists of (re-)defining data and behaviour globally.

    These two points are in furious contradiction, since redefining top-level definitions is pretty much the ultimate side effect. Every other top-level function can see this “action at a distance”.

    Let me be perfectly clear: ML and Haskell allow you to program using Lisp’s “every definition is subject to revision” style. Just stuff all your top-level definitions into mutable cells. The reason why it’s not done as frequently as in Lisp-land is because, perhaps, perhaps, this is actually a bad idea.

    1. 6

      redefining top-level definitions is pretty much the ultimate side effect.

      Funny, I’d consider the alternative, restarting a process, to be “the ultimate side effect”.

      The reason why it’s not done as frequently as in Lisp-land is because, […]

      Because defaults matter. Wrapping every definition with performUnsafeIO/readMVar would be prohibitive ceremony.

      perhaps, perhaps, this is actually a bad idea.

      Why? My experience is that it’s a really great idea.

      1. 2

        Funny, I’d consider the alternative, restarting a process, to be “the ultimate side effect”.

        How so? There’s no local reasoning about the old process being defeated, precisely because you have discarded the old process.

        Wrapping every definition with performUnsafeIO/readMVar would be prohibitive ceremony.

        It pales in comparison to the ceremony of re-proving a large chunk of your program correct, because a local syntactic change had global consequences on the program’s meaning.

        Why? My experience is that it’s a really great idea.

        Because… how do you guarantee anything useful about something that doesn’t have a stable meaning?

        1. 3

          There’s no local reasoning about the old process being defeated

          Does your program exist in isolation? Useful programs needs to deal with stateful services, such as a database. Why shouldn’t your compiler/language/runtime offer tools for dealing with the same set of problems?

          It pales in comparison to the ceremony of re-proving a large chunk of your program correct

          I don’t understand this point at all. If your “proof” is a type checker, then just run the type checker on the new code…

          how do you guarantee anything useful about something that doesn’t have a stable meaning?

          You don’t. You stop changing the meaning when you want to make guarantees about it. You don’t need all guarantees at all times, especially during development.

          Again, consider stateful services. Do you run tests against the production database? Or do you use a fresh/empty database? If you need something to stand still, you can hold it still.

          1. 2

            Useful programs needs to deal with stateful services, such as a database.

            When they’re running, not when I’m writing them.

            Why shouldn’t your compiler/language/runtime offer tools for dealing with the same set of problems?

            It isn’t immediately clear to me what kind of problems you’re thinking of, that are best solved by arbitrarily tinkering with the state of a running process.

            If your “proof” is a type checker, then just run the type checker on the new code…

            It is not. Some things I prefer to prove by hand, since it takes less time.

            You don’t need all guarantees at all times, especially during development.

            It’s during development that I need those guarantees the most, since, after a program has been deployed, it’s too late to fix anything.

            1. 2

              When they’re running, not when I’m writing them.

              Your production system is always running. Is it not?

              what kind of problems you’re thinking of, that are best solved by arbitrarily tinkering with the state of a running process

              Who says it is “arbitrary”? I very thoughtfully decide when to change the state of my running processes. When you change one service out of 100 in a production environment, is that not “rebinding” a definition? Why should programming in the small be so different than programming in the large?

              Have you ever worked on a UI with hot-loading? It’s so nice to re-render the view without having to re-navigate to where I was. Or to write custom code to snapshot and recover my state between program runs.

              What about a game? What if I want to tweak a monster’s AI routine without having to find through a dozen other monsters to get to the exact situation I want to test? I should be able to change the monster’s behavior at runtime. Great idea to me.

              Proof is useful, but it’s not everything, and it’s not even clear that it’s meaningfully harmed by having added dynamism. Instead of proving X, you can prove X if static(Y).

              1. 5

                This thread is a direct illustration of the incommensurability between the systems paradigm and the programming language paradigm, as described in Richard Gabriel’s “The Structure of a Programming Language Revolution” https://www.dreamsongs.com/Files/Incommensurability.pdf

                1. 1

                  What a wonderful piece of writing. Thanks a lot for sharing this.

          2. 1

            I’d just like to chip in here with one word

            “Facebook”

            The Facebook “Process” is a great example of something that never needs restarting but rather features and services are changed or added to the application while it is running in front of our eyes - no page refresh required in the Web version at least.

            I’d say that from a customer’s perspective this is a really good thing, and continuous end-user improvement is the long tail of continuous integration where nobody need do a reinstall ever again.

            I realize that this is largely philosophical at this point, but we are starting to have the tools to make this possible in a more general setting.

            For me as a user it’s a good thing I think, since I don’t need to lose context in huge point releases. Oh look, edit mesh just appeared on my toolbar. I wonder what that does?

            So I guess I’m with Brandon on this one.

        2. 2

          Immutability here refers to data and how the state is managed in the application. Since Clojure uses immutable data structures, majority of functions are pure and don’t rely on outside state. This makes it easy to reload any functions without worrying about your app getting in a bad state.

        3. 3

          lisp are the only language which are the only languages that I know which can really make heavy use of REPL

          1. 3

            Haskell and Ocaml are also pretty decent at this.

            1. 1

              The difference is that there is tight integration between the editor and the REPL in Lisps. When I work with Clojure, my IDE is connected to the running application. I can inspect and reload any code in the application straight from the editor.

              1. 0

                From what I understand stativ typing slow you too much

              2. 2

                ipython is excellent, but it doesn’t quite support incrementally writing a module in the same way as a lisp. E.g. if foo.py contains from bar import func, redefining func in bar is difficult.

                Julia has a lovely REPL experience.

                Smalltalks are arguably REPL environments: you can evaluate code anywhere, and the debug/edit/continue experience is amazing.

                1. 2

                  With python you can get a little bit closer using Jupyter, since it blends an editing environment with a REPL more than using iPython directly. I try to remember to use it instead of iPython when I need a REPL. It’s especially nice if you’re doing exploratory data work, because you can draw graphs and charts.

                  1. 0

                    Smalltalk is strange you have not codesource file. Wonder about Julia

                  2. 0

                    I should have precisr for building a profram

                  3. 2

                    This is an excellent writeup on the benefits you see from having the application language be the application.

                    I’d also like to add a little to this. For some time, I have been working on a multi-user version of LISP scalable to tens of thousands of concurrent sessions on a single machine.

                    In this system, given sufficient privilege, any user or agent in the system may inspect, reflect or inject definitions in one, any or all of the other environments, whether they are connected and running or not.

                    As you can imagine, being able to do this from a simple REPL provides amazing power to you the application developer, but certainly comes with huge responsibility. When you push a definition out, it really is live in that moment.

                    The nice thing is, as this author says, you can play in your sandbox and keep micro-testing until you feel comfortable, then try giving the new definition to one user agent to see how it works before finally committing it to everyone.

                    That is the beauty of the REPL.

                    It still scares the pants off me though!

                    1. 1

                      What would be an example of a test you don’t need if you have a repl?

                      1. 2

                        I find that in Clojure it’s a combination of having a repl and the language being backed by immutable data structures. Most functions are pure, so I can write a function and play with it in the REPL as I’m working on it. Once I finish writing it, I know that it does exactly what I want. The only tests I tend to have are API level tests. I find that repl completely replaces TDD with a much tighter feedback loop.

                        1. 2

                          And then you throw away the test? What happens if you change the function? It seems a lot of tests could be discarded with any programming paradigm if we promise to never change the code again.

                          1. 1

                            When I change the function I’m going to test it in the REPL again. You have to consider how Clojure is written though. Most functions are general purpose transformations of data, and you put those together at top level to produce domain specific business logic. Typically, when you want to do something different it will be accomplished by chaining these generic functions differently, and that will happen at API level where your tests are.

                            I’ve been working with Clojure professionally for about 6 years now, and I’m still surprised how few tests are needed compared to other languages I’ve used previously.