This section is optional, but highly recommended. It requires that you’re a user of GNU Emacs. If you aren’t, don’t worry… you can forge ahead and come back in case you ever do become an Emacs user. (If you’re more familiar with Vi/Vim style editing, I hear good things about Spacemacs…)
Remember all the way back when we were working on the IRC bot? So you may have noticed while updating that section that the start/stop cycle of hacking isn’t really ideal. You might either edit a file in your editor, then run it, or type the whole program into the REPL, but then you’ll have to spend extra time copying it to a file. Wouldn’t it be nice if it were possible to both write code in a file and try it as you go? And wouldn’t it be even better if you could live edit a program while it’s running?
Luckily, there’s a great Emacs mode called Geiser which makes editing and hacking and experimenting all happen in harmony. And even better, 8sync is optimized for this experience. 8sync provides easy drop-in "cooperative REPL" support, and most code can be simply redefined on the fly in 8sync through Geiser and actors will immediately update their behavior, so you can test and tweak things as you go.
Okay, enough talking. Let’s add it! Redefine run-bot like so:
(define* (run-bot #:key (username "examplebot") (server "irc.freenode.net") (channels '("##botchat")) (repl-path "/tmp/8sync-repl")) (define hive (make-hive)) (define irc-bot (bootstrap-actor hive <my-irc-bot> #:username username #:server server #:channels channels)) (define repl-manager (bootstrap-actor hive <repl-manager> #:path repl-path)) (run-hive hive '()))
If we put a call to run-bot at the bottom of our file we can call it, and the repl-manager will start something we can connect to automatically. Horray! Now when we run this it’ll start up a REPL with a unix domain socket at the repl-path. We can connect to it in emacs like so:
M-x geiser-connect-local <RET> guile <RET> /tmp/8sync-repl <RET>
Okay, so what does this get us? Well, we can now live edit our program. Let’s change how our bot behaves a bit. Let’s change handle-line and tweak how the bot responds to a botsnack. Change this part:
;; From this: ("botsnack" (respond "Yippie! *does a dance!*")) ;; To this: ("botsnack" (respond "Yippie! *catches botsnack in midair!*"))
Okay, now let’s evaluate the change of the definition. You can hit "C-M-x" anywhere in the definition to re-evaluate. (You can also position your cursor at the end of the definition and press "C-x C-e", but I’ve come to like "C-M-x" better because I can evaluate as soon as I’m done writing.) Now, on IRC, ask your bot for a botsnack. The bot should give the new message… with no need to stop and start the program!
Let’s fix a bug live. Our current program works great if you talk to your bot in the same IRC channel, but what if you try to talk to them over private message?
IRC> /query examplebot <foo-user> examplebot: hi!
Hm, we aren’t seeing any response on IRC! Huh? What’s going on? It’s time to do some debugging. There are plenty of debugging tools in Guile, but sometimes the simplest is the nicest, and the simplest debugging route around is good old fashioned print debugging.
It turns out Guile has an under-advertised feature which makes print debugging really easy called "pk", pronounced "peek". What pk accepts a list of arguments, prints out the whole thing, but returns the last argument. This makes wrapping bits of our code pretty easy to see what’s going on. So let’s peek into our program with pk. Edit the respond section to see what channel it’s really sending things to:
(define-method (handle-line (irc-bot <my-irc-bot>) message speaker channel line emote?) ;; [... snip ...] (define (respond respond-line) (<- (actor-id irc-bot) 'send-line (pk 'channel channel) respond-line)) ;; [... snip ...] )
Re-evaluate. Now let’s ping our bot in both the channel and over PM.
;;; (channel "##botchat") ;;; (channel "sinkbot")
Oh okay, this makes sense. When we’re talking in a normal multi-user channel, the channel we see the message coming from is the same one we send to. But over PM, the channel is a username, and in this case the username we’re sending our line of text to is ourselves. That isn’t what we want. Let’s edit our code so that if we see that the channel we’re sending to looks like our own username that we respond back to the sender. (We can remove the pk now that we know what’s going on.)
(define-method (handle-line (irc-bot <my-irc-bot>) message speaker channel line emote?) ;; [... snip ...] (define (respond respond-line) (<- (actor-id irc-bot) 'send-line (if (looks-like-me? channel) speaker ; PM session channel) ; normal IRC channel respond-line)) ;; [... snip ...] )
Re-evaluate and test.
IRC> /query examplebot <foo-user> examplebot: hi! <examplebot> Oh hi foo-user!