Oooh now that is interesting. What I by mean stuff I don't even know that I don't know :)

Yes mine doesn't handle that, it is the same as jupyter there. Smalltalk is supposed to be best at interactive development, I wonder if it will update the old closures. I don't know it to try, but I do know Common Lisp which is also supposed to be quite good, and fwiw it behaves the same, new closures have the new code, but the old ones are not updated:

  (use-package :serapeum)
  (defun adder (x)
    (flet ((inner (y) (/ x y)))
      #'inner))
  
  (defparameter *adders* (dict))
  
  (defun add (x y)
    (ensure (@ *adders* x) (adder x))
    (funcall (@ *adders* x) y))

  (add 3 6) ; => 1/2
  (add 3 9) ; => 1/3
  ;; change / to + in inner
  (add 4 9) ; => 13
  (add 3 10) ; => 3/10

Interesting. It appears that not many systems have been designed to enable hot reload in a thorough way.

Here's another fun complication: what if I have a decorator that performs a code transform on the source code of the decorated function? If the code is changed, I would like to automatically re-run the transform on the new code. I made a (kind of awkward) protocol for this: https://github.com/breuleux/jurigged?tab=readme-ov-file#cust.... It's a little confusing even for me, so I should probably review it.

Wow it looks like you are taking reloading to another level I hadn't even considered.

For example in your elephant:main.py test, in swanky python I run do(3): ['Paint 3 canvasses', 'Sing 3 songs', 'Dance for 3 hours']

change songs to songz, and now do(3) is: ['Paint 3 canvasses', 'Sing 3 songs', 'Dance for 3 hours', 'Sing 3 songz']

Rather than changing the earlier songs to songz as jurigged manages to. But any lisp environment would behave the same, we don't have the idea of:

> 3. When a file is modified, re-parse it into a set of definitions and match them against the original, yielding a set of changes, additions and deletions.

We are just evaling functions or whatever sections of code you say to eval, not parsing files and seeing what was modified. So in some cases we might need to make a separate unregister function and call that on the old one. Like in emacs if you use advice-add (adds before, after, and other kinds of hooks to a function), you can't just change the lines adding an advice and save the file to have it modify the old advice, you need to explicitly call advice-remove to unset the old advice, then advice-add with your new advice, if you want to modify it while running without restarting.

When I eval a function again I am evaling all decorators again, in your readme you write the downsides of that:

> %autoreload will properly re-execute changed decorators, but these decorators will return new objects, so if a module imports an already decorated function, it won't update to the new version.

But I think I am handling that, or maybe you have other cases in mind I am missing? ie in a.py:

  def dec(f):
      def wraps():
          return f() + 2
      return wraps

  @dec
  def reload_me():
      return 1
Then in b.py, from a import reload_me, change reload_me in a and slime-eval-defun it, and b is using the new version of reload_me. Basically for all (__module__, __qualname__) I am storing the function object, and all old versions of the function object. Then when there is a new function object with that name I update the code, closure and other attributes for all the old function objects to be the same as the new one.

I'll look into maybe just integrating jurigged for providing the reloading within swanky python. I was using the ipython autoreload extension at first, but ran into various problems with it so ended up doing something custom still mostly based on ipython, which is working for me in practice for now. So as long as I don't run into problems with it I'll focus on the many other parts of swanky python that need work, but sooner or later when I inevitably run into reloading problems I'll evaluate whether to just switch reloading to use jurigged.