Live coding tricks for CHICKEN Scheme
Estimated reading time: 7 minutes
First of all, I don’t use any fancy tool like SLIME or Geiser as I don’t use a complex editor like emacs or vim, but these tricks can probably apply to them as well.
My solution involves using csi’s REPL itself because it has a few really good features for debugging, which are sadly not readily available outside of it yet.
I run it within Ma, an ACME clone, but it would work the same with emacs’s run-scheme for example. The only feature you need from your editor is a way to send code to the REPL.
Keep your REPL running
The main principle, if your program is a continuously running loop, is to just run it in a different SRFI-18 thread.
I start by writing a (possibly empty) main procedure running the loop and run it in a thread, I also add a bit of code to make the program behave correcly when building a release or running it outside a REPL.
(import srfi-18)
(define (main) (main))
(define game-thread (thread-start! main))
(cond-expand ((or chicken-script compiling) (thread-join! game-thread))
(else))
I also keep a little expression handy to restart the game thread when it dies:
#;
(when (eqv? 'terminated (thread-state game-thread))
(set! game-thread (thread-start! main)))
The #;
at the beginning is an expression comment,
I feel they aren’t that well known, which is a shame since they are so useful!
csi option and toplevel commands
Now that the little boilerplate code is written, you can start running the interpreter. I highly recommend you run it with the -:x
command line flag. With it, exceptions from threads are delivered to the primordial thread (where the REPL runs).
You can inspect exceptions with csi’s toplevel commands ,exn
, ,c
and ,f N
(nice tools that almost nobody uses!)
Lets run some erroneous code as an example of these commands:
#;1> (define (erroneous x) (if (< x 0) 0 (/ (erroneous (- x 1)) x)))
#;2> (erroneous 4)
Error: (/) division by zero
0
0
Call history:
<eval> [erroneous] (/ (erroneous (- x 1)) x)
<eval> [erroneous] (erroneous (- x 1))
<eval> [erroneous] (- x 1)
<eval> [erroneous] (< x 0)
<eval> [erroneous] (/ (erroneous (- x 1)) x)
<eval> [erroneous] (erroneous (- x 1))
<eval> [erroneous] (- x 1)
<eval> [erroneous] (< x 0)
<eval> [erroneous] (/ (erroneous (- x 1)) x)
<eval> [erroneous] (erroneous (- x 1))
<eval> [erroneous] (- x 1)
<eval> [erroneous] (< x 0)
<eval> [erroneous] (/ (erroneous (- x 1)) x)
<eval> [erroneous] (erroneous (- x 1))
<eval> [erroneous] (- x 1)
<eval> [erroneous] (< x 0) <--
Woops, division by zero! If the exception message isn’t clear enough, or that some of its internal components are interesting (such as the return code of an HTTP request) you can pretty print the exception object with ,exn
.
Also note that ,exn
will return the exception object, which is then available through the REPL history variables (#2
in this example).
#;2> ,exn
condition: (exn arithmetic)
exn
message: "division by zero"
arguments: (0 0)
call-chain: (#("<eval>" (/ (erroneous (- x 1)) x) #<frameinfo>) #("<eval>" (erroneous (- x 1)) #<framei...
location: /
arithmetic
You can then expand the call chain with ,c
:
#;3> ,c
15:[] <eval> [erroneous] (/ (erroneous (- x 1)) x)
14:[] <eval> [erroneous] (erroneous (- x 1))
13:[] <eval> [erroneous] (- x 1)
12:[] <eval> [erroneous] (< x 0)
11:[] <eval> [erroneous] (/ (erroneous (- x 1)) x)
10:[] <eval> [erroneous] (erroneous (- x 1))
9:[] <eval> [erroneous] (- x 1)
8:[] <eval> [erroneous] (< x 0)
7:[] <eval> [erroneous] (/ (erroneous (- x 1)) x)
6:[] <eval> [erroneous] (erroneous (- x 1))
5:[] <eval> [erroneous] (- x 1)
4:[] <eval> [erroneous] (< x 0)
3:[] <eval> [erroneous] (/ (erroneous (- x 1)) x)
2:[] <eval> [erroneous] (erroneous (- x 1))
1:[] <eval> [erroneous] (- x 1)
*0:[] <eval> [erroneous] (< x 0)
---
x19: -1
And inspect a specific call point with ,f N
, which will show all the local variables for this point (in this case, x = 0
):
#;3> ,f 3
15:[] <eval> [erroneous] (/ (erroneous (- x 1)) x)
14:[] <eval> [erroneous] (erroneous (- x 1))
13:[] <eval> [erroneous] (- x 1)
12:[] <eval> [erroneous] (< x 0)
11:[] <eval> [erroneous] (/ (erroneous (- x 1)) x)
10:[] <eval> [erroneous] (erroneous (- x 1))
9:[] <eval> [erroneous] (- x 1)
8:[] <eval> [erroneous] (< x 0)
7:[] <eval> [erroneous] (/ (erroneous (- x 1)) x)
6:[] <eval> [erroneous] (erroneous (- x 1))
5:[] <eval> [erroneous] (- x 1)
4:[] <eval> [erroneous] (< x 0)
*3:[] <eval> [erroneous] (/ (erroneous (- x 1)) x)
---
x19: 0
2:[] <eval> [erroneous] (erroneous (- x 1))
1:[] <eval> [erroneous] (- x 1)
0:[] <eval> [erroneous] (< x 0)
Hack your define
The last little trick I use is a hack I wrote to make define
mutate existing procedures instead of rebinding their name to a new one. That way, even procedures passed as argument can be modified.
This version of define is available as an egg for CHICKEN 5, you can install it via chicken-install live-define
.
I suggest you only use it when running your code in the REPL, and that you don’t rely on its behaviour outside of live-coding purposes as it might break your code when some optimisations are enabled, such as inlining. It might also give very surprising results when used elsewhere than the toplevel.
To use it I just add this line right after all the usual import statements.
(cond-expand (csi (import live-define)) (else))
Here is a dumb example:
(import live-define)
(define (show f)
(lambda ()
(print "showing: " (f))))
(define (foo) "hello")
(define s (show foo))
(s) ; --> showing: hello
(define (foo) "goodbye")
(s) ; --> showing: goodbye