Hacker News new | ask | show | jobs
by Kessler83 1682 days ago
To see the REPL and debugger advantages of Common Lisp over other Lisps, Python, Haskell etc., you really should try for yourself in SLIME or SLY (a SLIME fork) imo.

But anyway, let's say I made an error somewhere and the program halts, putting me in the debugger. I can then examine each frame preceding the error. I can see every value going into my functions and jump around in the code generating those values. I change the code, while the debugger is still running, recompile it into the halted program and then pick a frame point right before the error occurred, to test if my changed code solves the problem. If it doesn't, I'll examine some more, perhaps start a second instance of the REPL to experiment a bit, then try another change in my code. All the while, I don't lose state. Once the problem is solved, my program will continue where it was as if the problem never happened.

For something like game programming, where you may be deep in the game and have encountered some rare combination of circumstances, this is simply invaluable.

5 comments

REPLs are great, and I always prefer a language with one than one that doesn’t, but being able to patch a running system is Lisp’s killer feature in my opinion. I’ve never seen another language that support that.

I had a bug in a long running program that would only show up after like six hours. I couldn’t replicate it in isolation because it had something to do with how state was being maintained. I set a conditional breakpoint right before the crash, inspected the stack, patch the function, and then had it pick back up by reëvaluating the current function call.

Damn thing just worked. It was amazing, and saved me so much time.

Smalltalk is designed around this functionality. In fact the whole Smalltalk system is a live instance that has been modified like that since development started.
Smalltalk is seriously awesome. I think it has a slight advantage over lisp in its fix-the-airplane-while-it-is-flying-at-3000 feet property.
How are the two not exactly the same in that respect?
Python programmer not accustomed to a "real" REPL. My closest experience would be Jupyter notebooks, where dangling state is more a liability than an asset.

> ...inspected the stack, patch the function, and then had it pick back up...

After you edit the live state of the program, how do you translate that into code sitting in source control? Do you save this blob of memory and pass that down through generations?

1. Stop the system.

2. Modify the source code containing the function.

2a. Save the file.

3. Copy function definition to the debugger’s repl, and evaluate it.

4. Resume the system from frame that calls the function.

But yes, you must be diligent about making sure the source files contain the code that is actually running, but that’s not much different than artifact tracking.

Usually one wants to make the change persistent in the sources.

Step a): One just evaluates from the sources, while changing them. Once done -> save sources.

Step b): Create a patch file, which changes the image. Such a patch file can be loaded while starting an image, until one decides to save a new image with the changes already loaded.

For example there is a new release for the commercial LispWorks IDE once every one or two years. Users usually get only patch files for bug fixes during the one or two years maintenance. Thus I have a directory for patches to LispWorks, which are loaded when I start the base image.

One way would be to make the change in the source file and then recompile/reload that one function. This is pretty easy to do with SLIME; you can highlight the snippet of code you want to evaluate, and then send that to your program to be evaluated on-the-fly.
This is one of the fundamental features of the BEAM (the Erlang/Elixir virtual machine) as well.
Almost all Smalltalks, especially the image based ones support dynamic run-time patches.
Where would you recommend getting started with Smalltalk on Linux today?
Squeak or Pharo if you want the full image-based experience. Some distros come with Squeak but the packaging can be confused and the versions are often old. A slow and slightly janky but surprisingly useable Squeak experience can be had in a web browser at https://squeak.js.org/run/ . Older Squeak versions tend to run faster in SqueakJS.

There are tutorials all over the place however as Smalltalk is a system that, like Lisp, has dialects, they tend to be system specific. Lots of material at http://stephane.ducasse.free.fr/FreeBooks.html .

jdougan already mentioned Pharo. There's a MOOC here : https://mooc.pharo.org/
FORTH (indirect threaded implementations at least) allows to redefine words while the system is running (fingers crossed).

Just because that feature is available and at one time was desirable (when machines were so slow, that compiling would break the flow), doesn't mean one needs to or should use it today.

Compiling can certainly still break the flow depending on the language and size of the project. Just ask Google.
I believe .NET 6 allows hot reloading, and Erlang already has that feature too
You can actually do something similar (much more limited, it sounds like) on Java, of all things.

https://devblogs.microsoft.com/java/hot-code-replacement-for...

As long as it doesn't change the class signature, you can happily edit-save-replace code, and it jumps to the top of the method you're editing. It's basically instant, so you don't suffer the javac and JVM startup cost.

I don't think I can do the same in more dynamic languages, like Ruby or Python. Or, maybe, my IDEs don't support it (IntelliJ suite).

That’s really cool to see. For Ruby, pry is usually the REPL to use for hot reloads. https://www.cbui.dev/an-introduction-to-pry-a-ruby-repl/
I hear this talked about quite a bit, but it still doesn't quite make sense to me why it's such a big deal: I know this situation where debugging-fix attempt loop is far more efficient if you don't lose state, but it's nowhere near a pervasive situation: when it comes up, I can generally write something quickly to re-create the necessary parts of the state automatically.

If I was doing that every day, or even every week, I could see it being a huge advantage to incorporate a solution in the language—but at least for me it just doesn't appear to be an obstacle that often (incidentally my background was initially in game programming, too).

> I can generally write something quickly to re-create the necessary parts of the state automatically.

I think this is where the trip up is. In a game, for example, that state is often exceptionally complex and getting everything right back to where you came from is usually only possible running the full game and recreating the same state.

It just doesn’t come up that often though.

In most cases it’s not hard to narrow down which aspects of state are relevant; you really only need to preserve everything for rare exceedingly subtle/deep bugs, which is a special case not a general usage kinda thing—and yet this feature is often discussed as revolutionary for programming in general.

I wonder what it is that prevents e.g. python from having a similar “persistent state in development” tool/repl? I’ve never really used lisp other than a MAL I did a few years ago for kicks so I’m not well versed in its dev tooling.
You can definitely do something similar with e.g. ipdb

  from ipdb import launch_ipdb_on_exception
  with launch_ipdb_on_exception():
    foo()
Ha! Very interesting, Thanks for feedback. The number of times I restarted a game after 1 line change + recompilation..
Having dealt with Angular dev server restarting for the tiniest code change, this sounds like a dream.