问题
Not sure if this is an emacs-SLIME issue or a CL issue or SBCL issue.
I've heard it said that the interactive nature of Lisp allows for changing a program while the program is running. Not knowing the specifics of what is meant by this, I tried the following, placing this in a separate file:
(defparameter repl-test-var 5)
(defun repl-test ()
(format t "repl-test-var is: ~a" repl-test-var)
(fresh-line)
(when (not (equal (read-line) "quit"))
(repl-test)))
Then I compile and run (repl-test)
and every time I press enter, I see the number 5
.
Without typing quit
at the REPL, I go back to my file and change the 5
to a 6
and compile again. Back at the REPL, pressing Enter still shows the 5
. If I type quit
and then run (repl-test)
again, now I see the 6
.
I have also tried loading as well as a combination of compile followed by loading using the SLIME shortcuts and they also have no effect until after I quit the running program and then start it again.
Is what I am trying to do either not possible or requiring another step in the code?
I realize it is a trivial example, but in more complex scenarios I may wish to do this.
回答1:
It appears that your code is not being reloaded while the REPL is busy because your SBCL image is single threaded. You can determine that your SBCL is single threaded by checking that :sb-thread is not present in *features*. Threaded vs unthreaded is determined when SBCL itself is compiled, so to get the behavior you desire you will need to either acquire an SBCL binary with threads enabled or compile SBCL with threads enabled.
A lack of threads can get in the way of some benefits of interactive development (as in your test, or if you want to develop a web program that has a server component running in the same image) but it still leaves some benefits open. Some handy aspects of interactive development that don't require your program to be actively "doing" anything for you to enjoy them include that you only have to reload the parts of your program that you changed, that this reloading does not cause the program to dump the data it has loaded (as a restart might), and that the REPL can be used as a handy window into your program's state and behavior.
回答2:
When functions are replaced in Lisp it doesn't mean "self-modifying code".
If a function is being executed in Lisp, the instruction pointer holds a reference to the function, and so the function continues to be a live object that cannot be reclaimed by the garbage collector.
When you redefine a function, what that means is that a new function object is associated with the name. When the function is called by name, the new function is used.
However, existing calls to the function which are in progress will continue to use the old function (which is not attached to the symbol any more). When the last thread stops executing that function, it will become garbage.
This is very similar to the "last close", in Unix, on an open file that has been deleted from the directory structure.
The issue shows up not only with multiple threads, but with simple recursion. If the function that is executing itself triggers the redefinition, then the function will continue with the old body. Moreover, Lisp allows the self-calls in recursive functions to avoid going through the name binding, but use a direct mechanism. If a recursive function redefines itself, the recursive calls still to be made in the same invocation may keep going to the same body.
More generally, Common Lisp allows compilers to generate efficient calls among functions that are in the same file. So you normally have to think of replacement of running code as being at the module level rather than individual function level. If functions A and B are in the same module, and A calls B, then if you merely replace B without replacing A, A may continue calling the old B (because B was inlined into A, or because A doesn't go through the symbol, but uses a more direct address for B). You can declare functions notinline
to suppress this.
CL has a number of features that provide a lot of control over the semantics of compilation and loading, so you can get the precise behaviors you need in your application.
回答3:
Emacs itself is a magnificent example of this. Change a function's definition (probably not something crucial like car
or self-insert-command
! :-) and watch its behavior change. See also in particular the advice facility of Emacs.
A compiled Lisp program, by definition, is not running an interactive REPL, though, so does not expose this behavior out of the box.
The problem with your example code is similar. It ties up the REPL, so there is no easy way to change the program's environment while it's running.
What makes Lisp so versatile (albeit not unique) is that (a) it gives you eval
; (b) it's very easy to write your own REPL on top of it; and (c) the best ones also offer documentation and/or hooks for modifying and extending their built-in REPL.
A more useful example program would eval
some input (keyboard input? Disk file? Authenticated download?) while it continues to run.
Outside of the "secret sauce" that is eval
, it is easy to find examples of programs which allow you to, say, upgrade a plug-in while the program continues to run compiled code, but Lisp does not provide any special facility for this -- the program needs to be built to support this.
回答4:
In order to update the running program, you have to find a way to interact with the running lisp image while your program is running. This can be done using multithreading, or by invocing the debugger.
To use multi-threading: Try starting the function like this:
(defparameter *thread* (sb-thread:make-thread #'repl-test))
If using emacs
+ slime
: Test the function in the *inferior-lisp*
buffer, and change it in the *slime-repl sbcl*
buffer.
Another test program that demonstrates changing a running program is this:
(defun update (i)
(+ i 1))
(defun hotpatch-test ()
(loop for i = 0 then (update i) do
(format t "~&i = ~d~%" i)
(fresh-line)
(sleep 5)))
Start it with
(defparameter *thread* (sb-thread:make-thread #'hotpatch-test))
Observe the numbers being printed, then change the definition of update
e.g. like
(defun update (i)
(+ i 2))
and see how the sequence of numbers output changes.
Finally, the thread may be killed with
(sb-thread:terminate-thread *thread*)
Update:
Another way to update the running program, without using multiprocessing, is to interupt the program with C-c
(or C-c C-c
in slime), load/enter new code while in the debugger, and then chose the continue
restart in order to continue running the program from where it was interupted.
来源:https://stackoverflow.com/questions/20259845/changing-a-program-while-it-is-running