Live data from Hacker News

The one about Lisp interactivity

blog.fogus.me

41–50 of 51 posts

Re: The one about Lisp interactivity

#41
post #4

The thing I still don't understand about REPL driven development is how you manage state, and how you manage threads. If I have a task running and it depends on a collection of global variables, those global variables now need some machinery around them so that they can be edited from the REPL thread. If you replace a function, there now needs to be decisions made about when you now begin usage of the new function. T…

IMO this is one of the main benefits of Clojure from a lisp-er perspective: it actually takes multithreading seriously in its design. It basically chose the "yes add the machinery" option, but built the machinery into the design of the language.

For instance, vars (i.e. global bindings) are mutable storage containers that can be bound on a per-thread basis. `(def foo "bar")` creates a new var and interns it in the current namespace, binding a root value "bar". Using it in code looks up the current value of it, either in thread-local storage or in the root binding. Any thread looking up the root binding of the var will see a newly bound value immediately.

However! Data in Clojure is immutable, which means that even if a running program started by looking up the current value of a var (or any of the other concurrency-safe containers in Clojure), once it has that value it is guaranteed not to change underneath you.

e.g. if you have a process that looks up the current value of a var and does some work with it for 5s, an in-flight process will not be altered if the var is redefined in the middle of the work. Other processes could even be kicked off that would alter the var, and it will have no affect on each other unless you explicitly synchronize, since the data inside the var is immutable.

Re: The one about Lisp interactivity

#42
post #28
post #27

Earlier quoted context omitted.

Doing a full reload of the system once in a while (especially after large changes) also helps. Just because you can keep a REPL open for days doesn't mean that's always the best idea.

Another way to deal with that is patching the running Lisp and "undefine" no longer needed functions, variables, classes, ...

I have always found that a weak point. Is there an "opposite" key binding to C-c C-c than will undefine/unintern an entity?

Re: The one about Lisp interactivity

#43
post #28

Earlier quoted context omitted.

Another way to deal with that is patching the running Lisp and "undefine" no longer needed functions, variables, classes, ...

I have always found that a weak point. Is there an "opposite" key binding to C-c C-c than will undefine/unintern an entity?

Can't remember what SLIME supports, but LispWorks has Undefine as an Editor command and in its Editor menus. It can undefine more than just functions.

Re: The one about Lisp interactivity

#44
post #28

Earlier quoted context omitted.

Another way to deal with that is patching the running Lisp and "undefine" no longer needed functions, variables, classes, ...

I have always found that a weak point. Is there an "opposite" key binding to C-c C-c than will undefine/unintern an entity?

C-c C-u is bound to `slime-undefine-function`, though it prompts you for the name of the function and doesn't determine it by the present context. `slime-unintern-symbol` is also present but not bound by default and also prompts for the name.

Re: The one about Lisp interactivity

#45
post #7

Earlier quoted context omitted.

> how you manage state It's already done for you by the runtime: it holds the state in the process memory. Now, with edit-compile-run-print loop if you want your state to persist between runs, you gotta implement some scheme of data persistence. > it depends on a collection of global variables, those global variables now need some machinery around them so that they can be edited from the REPL thread. In Erlang, you c…

What is ECRPL?

"Edit-compile-run-print loop", a non-standard (but fairly clear) term I've used in the beginning of my comment.

Re: The one about Lisp interactivity

#47
post #16

Earlier quoted context omitted.

I would also not normally connect to a REPL in a production environment, but it can come in handy. One can certainly attach a debugger to a running process in production, I've done it several times to debug gnarly issues.

I’ve done it in Elixir a handful of times to try to understand a bug that I couldn’t reproduce in my dev environment. And a few times to run some queries for a client and dump them out to CSV.

You used an elixir debugger on production?

Re: The one about Lisp interactivity

#48

In the opening paragraphs, this post indicates that this other post of David Vujuc [1] falls victim to common misconceptions about what a REPL is. I'm not sure what misconceptions this post is referring to; perhaps I also have these misconceptions. But I also don't think this post clarifies that. Could anyone here make it more explicit? [1]: https://davidvujic.blogspot.com/2022/08/joyful-python-with-r...

The Vujic post describes repl-driven development as "[evaluating] variables, code-blocks, functions, or an entire module [to] get instant feedback, just by hitting a key combination in your favorite code editor." Speaking from the perspective of long experience with Lisp and Smalltalk environments, I agree with fogus here: this is a misconcpetion--or at least an impoverished version of repl-driven development. The ex…

unlike any implementation of Clojure I'm aware of, both Smalltalk and Common Lisp implementations support handling an error or other exception by starting up a nested repl...

I think that we've not taken the development of REPLs seriously enough in the Clojure community and have left a lot of power in the past because of it. Things could get a lot better for the state of nested REPLs and I've been knee-deep in explorations along one vector lately that I hope might push that angle just a little bit further along.

Re: The one about Lisp interactivity

#49
post #48

Earlier quoted context omitted.

The Vujic post describes repl-driven development as "[evaluating] variables, code-blocks, functions, or an entire module [to] get instant feedback, just by hitting a key combination in your favorite code editor." Speaking from the perspective of long experience with Lisp and Smalltalk environments, I agree with fogus here: this is a misconcpetion--or at least an impoverished version of repl-driven development. The ex…

unlike any implementation of Clojure I'm aware of, both Smalltalk and Common Lisp implementations support handling an error or other exception by starting up a nested repl... I think that we've not taken the development of REPLs seriously enough in the Clojure community and have left a lot of power in the past because of it. Things could get a lot better for the state of nested REPLs and I've been knee-deep in explor…

That seems like a tall order, though a worthwhile one.

You of course need a way to create a repl with visibility into the dynamic context from which it's created, which implies representing dynamic context in a way that's convenient for inspection.

If you want dynamic editing and recovery features similar to those of CL and Smalltalk systems, you'll also need the dynamic context to be represented in a way that permits mutation, which is a little awkward, considering Clojure's understandable preference for immutability and thread safety.

If you want to be able to restart from a user-selected stack frame, then you need something that serves the same purpose as Common Lisp's conditions and restarts, or Smalltalk's activation records. Maybe you could borrow the design of the Common Lisp condition system.

If you want to be able to handle dynamic redefinition gracefully, then you need something like Common Lisp's and Smalltalk's ability to automatically find and update live instances of a redefined type, which in turn requires system-level features for tracking changes to the type definitions of arbitrary instances, and a facility for automatically reinitializing them on-demand (which in turn requires the very breakloop features that you're building, because sometimes reinitialization requires user input).

This set of features is kind of a ball of hair that is hard to get right by bolting it on after the language is designed. The Julia folks have been struggling with it for years now. They work well in Common Lisp and Smalltalk, but I think that's because the languages were designed around these kinds of features from the start. Language support for them in Common Lisp, for instance, is written into the ANSI standard.

Also, of course, if the system can reinitialize live instances, then they can't really be immutable or thread safe--at least not from the point of view of the development environment, because it has to be able to mutate them as-needed. Maybe they can still be immutable from Clojure's point of view, but that implies that the development environment is not bound by the same rules as the language that it implements.

That's doable, of course. No law of nature requires that the development environment obey the same rules as the language that it implements. For example, Leibniz, the development environment for the Newton version of Dylan, was written in Common Lisp.

Of course, that meant that those of use who wanted to modify and extend Leibniz had to know Common Lisp as well as Dylan. But that's not so different from the situation with Clojure and Java.

Re: The one about Lisp interactivity

#50
post #47

Earlier quoted context omitted.

I’ve done it in Elixir a handful of times to try to understand a bug that I couldn’t reproduce in my dev environment. And a few times to run some queries for a client and dump them out to CSV.

You used an elixir debugger on production?

Elixir REPL in production yeah. Not to modify anything, but to inspect the state of the world and figure out why some OTP apps were dying. I'm drawing a blank on the name of the build tool, but the "launch binary" it produces has an option to fire up a second copy of your app, keep the console alive, and connect it to the cluster as another node. Worked like a charm, although it's something I wouldn't want to do on a regular basis :)
Post reply on HN