Which lisp implementations allow me to modify code at runtime?

1.2k Views Asked by At

Lisp is homoiconic, meaning code can be treated as data. Which implementations allow me to do so at runtime? The following is an example of what I mean, in pseudocode:

(defun (my-func)
  (display "foo ")
  (display "bar ")
  (display "baz "))

(defun (main-loop)
  (my-func)
  (swap (first my-func) (second my-func))
  (main-loop))

That should repeatedly output "foo bar baz bar foo baz ".

3

There are 3 best solutions below

2
On BEST ANSWER

This is probably not the most elegant approach, but in common Lisp you can do something like this:

> (setq f '(defun foo () (princ "foo ") (princ "bar ") (princ "baz ")))
(DEFUN FOO NIL (PRINC "foo ") (PRINC "bar ") (PRINC "baz "))
> (eval f)
FOO
> (foo)
foo bar baz
NIL
> (defun frot ()
        ; Call foo (stored in f)
        (funcall (eval f))
        ; Swap the 1st and 2nd statements in foo
        (setf tmp (cadddr f))
        (setf (cadddr f) (cadr (cdddr f)))
        (setf (cadr (cdddr f)) tmp)))
FROT
> (frot)
foo bar baz
(PRINC "foo ")
> (frot)
bar foo baz
(PRINC "bar ")
> (frot)
foo bar baz
(PRINC "foo ")

This stores a Lisp function in f rather than executing it in situ, but it does illustrate the fact that a Lisp program is itself a Lisp data structure than can be dynamically manipulated and executed.

0
On

The other answers cover the question well.

From a practical level however if you are using Common Lisp and Slime and want to be able to compile code into your running program from Emacs you will need to tell Swank to update from inside your loop.

Add the following to your code and then add (update-swank) inside your loop.

(defmacro continuable (&body body)
  `(restart-case
       (progn ,@body)
     (continue () :report "Just Continue")))

(defun update-swank ()
  "Called from within the main loop, this keep the lisp repl working"
  (continuable
    (let ((connection (or swank::*emacs-connection*
                         (swank::default-connection))))
      (when connection
        (swank::handle-requests connection t)))))

This is one way to use the fact you can recompile live with your editor as in this video (sorry for plugging my own vid).

Another way (again with Slime) is to tell it to use a different thread for the communication. I prefer the former method however as opengl is very unstable when used across threads.

[More Details] The continuable macro in the code above catches any error and gives you the option to ignore it and continue. I find this really helpful and I often make mistakes in the repl and I don't want to 'abort' from an error as this would abort my main loop.

0
On

If you are modifying code as you describe, then you know something about the structure of the code. Since you know the structure of the code, you can parameterize that structure

(define (foo-er foo bar baz)
  (lambda ()
     (display foo)
     (display bar)
     (display baz)))

Then you can do the swapping by explicitly passing your arguments as:

(define (main-loop)
  (let looping ((foo "foo ") (bar "bar ") (baz "baz "))
    ((foo-er foo bar baz))
    (looping bar foo baz)))

> (main-loop)
foo bar baz bar foo baz foo bar baz ...

The CommonLisp version is analogous.

If you need to keep my-func around:

(define my-func #f)
(define (main-loop)
  (let looping ((foo "foo ") (bar "bar ") (baz "baz "))
    (set! my-func (foo-er foo bar baz)
    (my-func)
    (looping bar foo baz)))