3

Lets say I have a REPL to a process running my Common Lisp code. It could be running SWANK/SLIME.

I want to update a function defined with defun in my live process. The function may have captured some variables in a let binding. Essentially, this function is a closure.

How can I update the code in that closure without losing the data that it has captured?

2019-11-03: I selected one answer below, but I recommend reading all of the answers. Each has an interesting insight.

drudru
  • 4,933
  • 1
  • 19
  • 19

3 Answers3

4

You can't from the outside.

You could try to provide helper functionality for that in the same lexical scope. This might entail creating an ad hoc function registry therein.

Another way would be to use dynamic variables, but of course that just breaks open the closure.

Maybe relevant (from https://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg03277.html):

The venerable master Qc Na was walking with his student, Anton. Hoping to prompt the master into a discussion, Anton said "Master, I have heard that objects are a very good thing - is this true?" Qc Na looked pityingly at his student and replied, "Foolish pupil - objects are merely a poor man's closures."

Chastised, Anton took his leave from his master and returned to his cell, intent on studying closures. He carefully read the entire "Lambda: The Ultimate..." series of papers and its cousins, and implemented a small Scheme interpreter with a closure-based object system. He learned much, and looked forward to informing his master of his progress.

On his next walk with Qc Na, Anton attempted to impress his master by saying "Master, I have diligently studied the matter, and now understand that objects are truly a poor man's closures." Qc Na responded by hitting Anton with his stick, saying "When will you learn? Closures are a poor man's object." At that moment, Anton became enlightened.

Svante
  • 50,694
  • 11
  • 78
  • 122
4

Basically you can't and that's one of the reasons to avoid using DEFUNs in LETs.

One could create a new closure and try to copy the state from the old one into the new one.

One problem is that portable Common Lisp does not allow much dynamic access to closures. One can't 'go' into a closure and add something or replace something from the outside. There are no reflective or introspective operations defined for closures.

Thus everything you later want to do with a closure needs to be already present in the closure generating code.

Let's say you have this code:

(let ((foo 1))
  (defun add (n)
    n))

Now we decide that add is wrong and should actually add something?

We want the effect of this:

(let ((foo 1))
   (defun add (n)
     (+ n foo)))

How can we modify the original? We basically can't.

If we had:

(let ((foo 1))
  (defun get-foo ()
    foo)
  (defun add (n)
    n))

We could do:

(let ((ff (symbol-function 'get-foo))
      (fa (symbol-function 'add)))
  (setf (symbol-function 'add)
     (lambda (n)
       (+ (funcall fa n) (funcall ff)))))

This then defines a new function add which has access to the closure values via the old functions - captured in its own closure.

Style

Don't use LET enclosed DEFUNs:

  • they are hard to modify
  • the compiler does not see the DEFUNs as toplevel forms
  • the effects are difficult to control
  • what do we want to do with loading code multiple times?
  • they are difficult to debug
  • Common Lisp has ways to deal with global state.
Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
3

The other answers explain that you can't do what you are after: here are some practical reasons why you can't.

Consider a fragment of code like this:

(let ((a 1) (b 3) (c 2))
  (lambda (x)
    (+ (* a x x) (* b x) c)))

Let's imagine this is being compiled: what is a reasonable compiler going to do? Well, pretty obviously it can turn it into this:

(lambda (x)
  (+ (* 1 x x) (* 3 x) 2)

(and then probably into

(lambda (x)
  (+ (* x x) (* 3 x) 2))

and perhaps further into

(lambda (x)
  (+ (* x (+ x 3)) 2))

) before finally compiling it.

None of these transformations of the function body reference any of the lexical bindings introduced by the closure. The whole environment has been compiled away.

So if I now wanted to replace that function, in that environent, bu some other one, well, there's no environment any more: I can't.

Well, you could argue that that's an unduly simple case since the function doesn't mutate any of its closed-over variables. Well, consider something like this:

(defun make-box (contents)
  (values
   (lambda ()
     contents)
   (lambda (new)
     (setf contents new))))

make-box returns two functions: a reader and a writer, which both close over the contents of the box. And this shared state can't be fully compiled away.

But there's absolutely no reason at all why the closed over state, for instance, still knows that the variable being closed-over was called contents after make-box has been compiled (or even before). For instance the two functions might both reference some vector of lexical state, and both know that the thing that is contents in the source is the first element of that vector. All the names are gone, so it would not be possible to replace one of the functions sharing this state with some other one.

Further, in implementations with distinct compilers and interpreters, there's no reason at all why the interpreter should share a common representation of closed-over lexical state with the compiler (and both compiler and interpreter may have several different representations). And in fact the CL spec addresses this problem – the entry for compile says:

The consequences are undefined if the lexical environment surrounding the function to be compiled contains any bindings other than those for macros, symbol macros, or declarations.

and this caveat is there to deal with cases like this: if you have a bunch of functions which share some lexical state then, if the compiler has a different representation of lexical state than the interpreter, compiling just one of them is problematic. (I discovered this while trying to do some terrible thing with shared lexical state on a D-machine in about 1989, and some kind member of the committee explained my confusion to me.)


The above examples should convince you that replacing functions which share lexical state with other functions is not possible in any simple way. But, well, 'not possible in any simple way' is not the same as 'not possible'. For instance the specification of the language could simply say that this should be possible and require implementations to somehow make it work:

  • for the first example, an implementation might either choose not to compile the environment away thus reducing performance, or might choose to compile it away but keep a record of what it was so it could be reinstated around some new function, possibly to be compiled away again;
  • for the later examples, implementations might either choose to use simple, low-performance representations of the closed-over environment in all cases, or to use high-performance representations but keep a record of what the environment looked like so that replacement functions could be inserted into it;
  • in any case, the language would require new functionality to support this kind of redefinition, which would need to be carefully specified and would make the language larger and more complicated.

Both of these cases are really saying that implementations of the language either need to accept rather low performance, or require heroic techniques, and in either case there would be more to implement than there already was. Well, one of the goals of the Common Lisp effort was that, while heroic techniques are allowed, they should not be required for high performance. Additionally the language was already felt to be quite large enough. Finally implementors would almost certainly simply have rejected such a suggestion: they already had quite enough work to do, and did not want any more, especially at this kind of 'you will need to completely reengineer the compiler to do this' level.

So that is why, pragmatically, what you're after is not possible.