Hacker News new | past | comments | ask | show | jobs | submit login

Hmm, what advantage does Lisp offer here over Python?

  >>> 1 + foo(20)
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
  NameError: name 'foo' is not defined
  >>> def foo(a):
  ... return a + 21
    File "<stdin>", line 2
      return a + 21
           ^
  IndentationError: expected an indented block
  >>> def foo(a):
  ...   return a + 21
  ...
  >>> 1 + foo(20)
  42
  >>>
Mind the hilarious indentation error, as I had not touched the old-school REPL in ages.

In normal day to day operations, I do the same thing daily with Jupyter Notebooks. I get access to as much state as I need.

With notebooks workflow it is normal to forget to define something and then redefine in the next cell. You could redefine function signatures etc. Ideally then you move cells in the correct order so that code can be used as Run All.

I "feel" ridiculously productive in VS Code with full Notebook support + copilot. I can work across multiple knowledge domains with ease (ETL across multiple database technologies, NLP-ML, visualization, web scraping, etc)

Underneath it is same as working in old school Python REPL just with more scaffolding.




I have been playing again with CL recently and am doing some trivial web-scraping of an old internet forum. I don't use a REPL directly, but just have a bunch of code snippets in a lisp file that I tell my editor to evaluate (similar to Jupyter?). I haven't bothered doing any exception (condition) handling, and so this morning I found this in a new window:

   Condition USOCKET:TIMEOUT-ERROR was signalled.
      [Condition of type USOCKET:TIMEOUT-ERROR]

   Restarts:
    0: [RETRY-REQUEST] Retry the same request.
    1: [RETRY-INSECURE] Retry the same request without checking for SSL certificate validity.
    2: [RETRY] Retry SLIME interactive evaluation request.
    3: [*ABORT] Return to SLIME's top level.
    4: [ABORT] abort thread (#<THREAD tid=17291 "worker" RUNNING {1001088003}>)
plus the backtrace. This is in a loop that's already crawled a load of webpages and has some accumulated some state. I don't want a full redo (2), so I just press 0. The request succeeds this time and it continues as if nothing happened.


You got a lot of correct but verbose responses. Put in layman's terms you had to run 1 + foo(20) again. If 1 + foo(20) were replaced by a complex and long winded function you would have lost all of that state and needed to run it all again. What if 1 + foo(20) had to read several TB of data in a distributed manner. You would have to do that all again.

There are ways around this and of course you could probably develop your own crash loop system in python but in lisp you simply continue where it failed. It's already there.

You mention doing things in Jupyter and ETLs which are often long running. This could be hugely beneficial to you.


> Hmm, what advantage does Lisp offer here over Python?

It does have a clear advantage if instead of

    (+ 1 (foo 20))
we were doing

    (+ (long-computation-answering-the-ultimate-question-of-life-the-universe-and-everything)
       (foo 20))
(Reminder: we're dicussing Common Lisp here.)


From what I see in your example, you invoke the form again. In Common Lisp you don't need that. You can stay in a computation and fix&resume from within.


You are not fixing the issue in the dynamic context of a running program. Doesn't matter in this trivial example but is very noticeable when you have a loaded DB cache and a few hundred active network connections.


The advantage is that Python has just diagnosed the error and aborted the whole thing back to the top level, whereas in the Common Lisp, the entire context where the error happened is still standing. There are things you can do like interactive replace a bad value with a good value and re-try the failed computation.

In lispm's example, the problem is that there is no foo function, so (foo 20) cannot be evaluated. You have various choices at the debugger prompt; you can specify a different function, to which the same arguments will be applied. Or just specify a value to be used in place of the nonworking function call.

Being able to fix and re-try a failed expression could be valuable if you have a large running system with hundreds of megabytes or even gigabytes of data in the image, which took a long time to get to that state.


> Hmm, what advantage does Lisp offer here over Python?

In lisp, I never edit code at the REPL, yet the REPL is what enables me to edit code anywhere. I edit the source files and have my editor eval the changes I made in the source. This gets me the benefit that should my changes work, I don't have to retype them to get them into version control. This works because the Lisp REPL is designed to be able to switch into any existing package, apply code there, and also switch back to the CL-USER package after. My editor uses the same mechanism and only has to inject a single prefix (`in-package :xyz`) before it pastes the code I've selected for eval.

In Python, editing a method in a class inside some module (i.e., not toplevel) is less easy. At least, I haven't found any editor support for it. What I did find is the common advice to just reload the whole module/file.

Okay, so let's reload the whole module, then? Well, Python isn't really built for frequent module reloads and that can sometimes bite. In Common Lisp, the assumption that any code may be re-eval-ed is built in. For example, there's two ways of declaring a global value in CL: defvar and defparameter. The latter is simply an assignment of a value to a variable in the global scope, but the former is special. By default, `defvar` defines a variable only if it's not already defined. So that a CL source file may be loaded and reloaded any number of times without resetting a global variable.

Then there's classes. Oh my. Common Lisp has the most powerful (in terms of flexibility) OO system I know of. Not only can you redefine functions and methods, you can even redefine classes dynamically. Adding a property to a class adds that property to all existing objects of that class. Removing a property from a class removes it from all existing objects of that class. This feature is no longer CL-exclusive, but it is sufficient to offer a massive advantage over Python. I don't need to talk about method combinations, multi-methods and the many other cool features of the Common Lisp Object System here.

Then there's the debugging system. In Python, when an exception is thrown, it immediately unwinds the stack all the way up until it is first caught. So not only do you need to know beforehand where to catch what exception, if you get it wrong you cannot inspect the site of the error. In CL, a condition ("exception") does not unwind the stack until a restart is chosen. Not when it is caught, but rather when — after being caught — a resolution mechanism has been chosen. This allows interactive debugging (another cool CL feature) to inspect the stack frames at (and above) the site of error, redefine whatever code needs to be corrected, all before the error is allowed to unwind and destroy the stack. You still need to set-up handlers (and restarts) before the error happens, but you can be absolutely wildly lax and use catch-all handlers anywhere on the stack and restarts that take absolutely anything (even functions) at debug-time so you don't really need to be prescient with your error handling code unlike in Python.

I'm sure there's more, but I think this is pretty sufficient.


You're losing all the state since you're not being dropped in the closure where the error happened but in the end of the program.

To see the difference use some function counter() instead of 1 in the example.




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: