Search code examples
schemeinterpreterchicken-scheme

Change program code while running in Chicken Scheme


Is it possible to update the program code while it is being interpreted by csi, the Chicken Scheme Interpreter? If so, how?

So that I can interactively change part of the code and immediately see the effects of that changes. For example, suppose I have written the following program:

(define (loop)
   (print "Ciao")
   (rest 1)
   (loop))

(loop)

(assume (rest 1) has the effect of pausing the program for a second).

If I run this program, trough csi, it prints the string "Ciao" every second. If I change the string "Ciao" into something else, for example into "else", and I save the program code file, then csi continue interpreting the old program code, so I continuously see the string "Ciao". I'd like, in this case, that when I save the modified code with the string "Ciao" replaced by "else", csi continue its interpretation job by looking into the modified file, instead of the old file. So that I obtain as output some "Ciao" followed by some "else": the "else" start to appear when I replace "Ciao" by "else" in the source code.


Solution

  • In general, the answer is "don't". The way you're supposed to use the REPL is by evaluating piecemeal changes against it, then evaluating a function or two to make sure everything went as expected. Part of this approach is structuring your program so that it can be easily tested in pieces, which implies not automatically starting any infinite loops. In the specific case you're asking about, you could instead write

    (define (do-stuff)
       (print "Ciao"))
    
    (define (main-loop)
       (do-stuff)
       (rest 1)
       (main-loop))
    
    (define (start) (main-loop))
    

    You can now incrementally develop do-stuff, periodically evaluating new versions to your interpreter and calling them to make sure they work, then eventually calling start once you're confident that it's doing the right thing.

    You may get mileage out of this similar question asked about Common Lisp.

    As an aside, if you were using Common Lisp and SLIME, you could now do more or less what you proposed:

    (defun do-stuff ()
      (format t "Ciao~%"))
    
    (defun main-loop ()
      (loop (progn (do-stuff)
                   (sleep 1))))
    
    (main-loop)
    

    Starting that up would start printing Ciao on separate lines in your SLIME REPL. If you changed do-stuff to

    (defun do-stuff ()
      (format t "else~%"))
    

    then hit it with C-c C-c (the default binding for slime-compile-defun), you'd see your SLIME REPL start printing else lines in-flight.

    CL-USER> (main-loop)
    Ciao
    Ciao
    Ciao
    ; compiling (DEFUN DO-STUFF ...)else
    else
    else
    else
    else
    else
    ; Evaluation aborted on NIL. User break.
    CL-USER> 
    

    I'm not sure how to accomplish the same thing in Scheme, but I'm reasonably sure it's possible.

    All that being said, you sometimes want to run a program part of which is an infinite loop. A real world example would be while testing a TCP server of some sort. If you're in such a situation, and your desired workflow is

    1. Write file
    2. Run csi my-file.scm
    3. Edit file
    4. Kill csi
    5. Run csi my-file.scm
    6. goto 3

    and you basically just want to automate steps 4 through 6, you'll need an external tool to do it for you. Take a look at entr or hsandbox (the latter doesn't have Scheme support out of the box, but it doesn't look like it would be too hard to add).