# with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
# A copy of the license is included in the section entitled ``GNU
# Free Documentation License''.
-#
+#
# A copy of the license is also available from the Free Software
# Foundation Web site at http://www.gnu.org/licenses/fdl.html
-#
-# Altenately, this document is also available under the Lesser General
+#
+# Alternately, this document is also available under the Lesser General
# Public License, version 3 or later, as published by the Free Software
# Foundation.
-#
+#
# A copy of the license is also available from the Free Software
# Foundation Web site at http://www.gnu.org/licenses/lgpl.html
#+BEGIN_SRC scheme
(define-class <my-irc-bot> (<irc-bot>))
- (define-method (handle-line (irc-bot <my-irc-bot>) speaker channel
- line emote?)
+ (define-method (handle-line (irc-bot <my-irc-bot>) message
+ speaker channel line emote?)
(if emote?
(format #t "~a emoted ~s in channel ~a\n"
speaker line channel)
(8sync uses GOOPS to define actors.)
We extended the handle-line generic method, so this is the code that
will be called whenever the IRC bot "hears" anything.
+This method is itself an action handler, hence the second argument
+for =message=, which we can ignore for now.
+Pleasantly, the message's argument body is passed in as the rest of
+the arguments.
+
For now the code is pretty basic: it just outputs whatever it "hears"
from a user in a channel to the current output port.
Pretty boring!
(channels '("##botchat")))
(define hive (make-hive))
(define irc-bot
- (bootstrap-actor* hive <my-irc-bot> "irc-bot"
- #:username username
- #:server server
- #:channels channels))
+ (bootstrap-actor hive <my-irc-bot>
+ #:username username
+ #:server server
+ #:channels channels))
(run-hive hive '()))
#+END_SRC
Actors are connected to something called a "hive", which is a
special kind of actor that runs and manages all the other actors.
Actors can spawn other actors, but before we start the hive we use
-this special "bootstrap-actor*" method.
+this special =bootstrap-actor= method.
It takes the hive as its first argument, the actor class as the second
-argument, a decorative "cookie" as the third argument (this is
-optional, but it helps with debugging... you can skip it by setting it
-to #f if you prefer), and the rest are initialization arguments to the
-actor. bootstrap-actor* passes back not the actor itself (we don't
+argument, and the rest are initialization arguments to the
+actor.
+=bootstrap-actor= passes back not the actor itself (we don't
get access to that usually) but the *id* of the actor.
(More on this later.)
Finally we run the hive with run-hive and pass it a list of
We can run it like:
#+BEGIN_SRC scheme
-(run-bot #:username "some-bot-username") ; be creative!
+(run-bot #:username "some-bot-name") ; be creative!
#+END_SRC
Assuming all the tubes on the internet are properly connected, you
Change handle-line to this:
#+BEGIN_SRC scheme
- (define-method (handle-line (irc-bot <my-irc-bot>) speaker channel
- line emote?)
+ (define-method (handle-line (irc-bot <my-irc-bot>) message
+ speaker channel line emote?)
(<- (actor-id irc-bot) 'send-line channel
(format #f "Bawwwwk! ~a says: ~a" speaker line)))
#+END_SRC
want to invoke (a symbol), and the rest are arguments to the
"action handler" which is in this case send-line (with itself takes
two arguments: the channel our bot should send a message to, and
-the line we want it to spit out to the channel).
-
-(Footnote: 8sync's name for sending a message, "<-", comes from older,
-early lisp object oriented systems which were, as it turned out,
-inspired by the actor model!
-Eventually message passing was dropped in favor of something called
-"generic functions" or "generic methods"
-(you may observe we made use of such a thing in extending
-handle-line).
-Many lispers believe that there is no need for message passing
-with generic methods and some advanced functional techniques,
-but in a concurrent environment message passing becomes useful
-again, especially when the communicating objects / actors are not
-in the same address space.)
+the line we want it to spit out to the channel).[fn:send-message-provenance]
Normally in the actor model, we don't have direct references to
an actor, only an identifier.
like so:
#+BEGIN_SRC scheme
- (define-method (handle-line (irc-bot <my-irc-bot>) speaker channel
- line emote?)
+ (define-method (handle-line (irc-bot <my-irc-bot>) message
+ speaker channel line emote?)
(irc-bot-send-line irc-bot channel
(format #f "Bawwwwk! ~a says: ~a" speaker line)))
#+END_SRC
through our IRC bot.
Anyway, our current message handler is simply too annoying.
-What would be much more interesting is if we could recognize
-when an actor could repeat messages /only/ when someone is speaking
-to it directly.
-Luckily this is an easy adjustment to make.
-
-#+BEGIN_SRC scheme
- (define-method (handle-line (irc-bot <my-irc-bot>) speaker channel
- line emote?)
- (define my-name (irc-bot-username irc-bot))
- (define (looks-like-me? str)
- (or (equal? str my-name)
- (equal? str (string-concatenate (list my-name ":")))))
- (when (looks-like-me?)
- (<- (actor-id irc-bot) 'send-line channel
- (format #f "Bawwwwk! ~a says: ~a" speaker line))))
-#+END_SRC
-
-This is relatively straightforward, but it isn't very interesting.
What we would really like to do is have our bot respond to individual
"commands" like this:
To implement it, we're going to pull out Guile's pattern matcher.
#+BEGIN_SRC scheme
- (define-method (handle-line (irc-bot <my-irc-bot>) speaker channel
- line emote?)
+ (define-method (handle-line (irc-bot <my-irc-bot>) message
+ speaker channel line emote?)
(define my-name (irc-bot-username irc-bot))
(define (looks-like-me? str)
(or (equal? str my-name)
you're right:
#+BEGIN_SRC scheme
- (define-method (handle-line (irc-bot <my-irc-bot>) speaker channel
- line emote?)
+ (define-method (handle-line (irc-bot <my-irc-bot>) message
+ speaker channel line emote?)
(define my-name (irc-bot-username irc-bot))
(define (looks-like-me? str)
(or (equal? str my-name)
At the time of writing, venture capital awash startups are trying to
turn chatbots into "big business"... a strange (and perhaps absurd)
thing given chat bots being a fairly mundane novelty amongst hackers
- and teenagers everywhere in the 1990s.
-
-** An intermission: about live hacking
-
-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...)
-
-So you may have noticed while updating the last 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:
-
-#+BEGIN_SRC scheme
- (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> "irc-bot"
- #:username username
- #:server server
- #:channels channels))
- (define repl-manager
- (bootstrap-actor* hive <repl-manager> "repl"
- #:path repl-path))
-
- (run-hive hive '()))
-#+END_SRC
-
-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:
-
-#+BEGIN_SRC scheme
- ;; From this:
- ("botsnack"
- (respond "Yippie! *does a dance!*"))
-
- ;; To this:
- ("botsnack"
- (respond "Yippie! *catches botsnack in midair!*"))
-#+END_SRC
-
-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?
-
-#+BEGIN_SRC text
-IRC> /query examplebot
-<foo-user> examplebot: hi!
-#+END_SRC
-
-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:
-
-#+BEGIN_SRC scheme
- (define-method (handle-line (irc-bot <my-irc-bot>) speaker channel
- line emote?)
- ;; [... snip ...]
- (define (respond respond-line)
- (<- (actor-id irc-bot) 'send-line (pk 'channel channel)
- respond-line))
- ;; [... snip ...]
- )
-#+END_SRC
-
-Re-evaluate.
-Now let's ping our bot in both the channel and over PM.
-
-#+BEGIN_SRC text
-;;; (channel "##botchat")
-
-;;; (channel "sinkbot")
-#+END_SRC
-
-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.)
-
-#+BEGIN_SRC scheme
- (define-method (handle-line (irc-bot <my-irc-bot>) 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 ...]
- )
-#+END_SRC
-
-Re-evaluate and test.
-
-#+BEGIN_SRC text
-IRC> /query examplebot
-<foo-user> examplebot: hi!
-<examplebot> Oh hi foo-user!
-#+END_SRC
-
-Horray!
+ and teenagers everywhere a few decades ago.
+
+[fn:send-message-provenance]
+ 8sync's name for sending a message, "<-", comes from older,
+ early lisp object oriented systems which were, as it turned out,
+ inspired by the actor model!
+ Eventually message passing was dropped in favor of something called
+ "generic functions" or "generic methods"
+ (you may observe we made use of such a thing in extending
+ handle-line).
+ Many lispers believe that there is no need for message passing
+ with generic methods and some advanced functional techniques,
+ but in a concurrent environment message passing becomes useful
+ again, especially when the communicating objects / actors are not
+ in the same address space.
** Writing our own actors
(define (sleeper-loop actor message)
(while (actor-alive? actor)
(display "Zzzzzzzz....\n")
- ;; Sleep for one second
+ ;; Sleep for one second
(8sleep (sleeper-sleep-secs actor))))
(let* ((hive (make-hive))
#+END_SRC
We see some particular things in this example.
-One thing is that our <sleeper> actor has an actions slot.
+One thing is that our =<sleeper>= actor has an actions slot.
This is used to look up what the "action handler" for a message is.
-We have to set the #:allocation to either #:each-subclass or #:class.
-(#:class should be fine, except there is [[https://debbugs.gnu.org/cgi/bugreport.cgi?bug=25211][a bug in Guile]] which keeps
-us from using it for now.)
+We have to set the #:allocation to either =#:each-subclass= or
+=#:class=.[fn:class-bug]
The only action handler we've added is for =*init*=, which is called
implicitly when the actor first starts up.
Our while loop also checks "actor-alive?" to see whether or not
it is still registered.
In general, if you keep a loop in your actor that regularly yields
-to the scheduler, you should check this.
+to the scheduler, you should check this.[fn:actor-alive-deprecated-soon]
(An alternate way to handle it would be to not use a while loop at all
but simply send a message to ourselves with "<-" to call the
sleeper-loop handler again.
Let's try applying that to our own code by turning our manager
into a micromanager.
-#+END_SRC
#+BEGIN_SRC scheme
;;; Update this method
(define (manager-assign-task manager message difficulty)
everywhere as we are in this program... that's just to make the
examples more illustrative.)
+"<-reply" is what actually returns the information to the actor
+waiting on the reply.
+It takes as an argument the actor sending the message, the message
+it is in reply to, and the rest of the arguments are the "body" of
+the message.
+(If an actor handles a message that is being "waited on" but does not
+explicitly reply to it, an auto-reply with an empty body will be
+triggered so that the waiting actor is not left waiting around.)
+
+The last thing to note is the call to "self-destruct".
+This does what you might expect: it removes the actor from the hive.
+No new messages will be sent to it.
+Ka-poof!
+
Running it is the same as before:
#+BEGIN_SRC scheme
worker> Whew! Free at last.
#+END_SRC
-"<-reply" is what actually returns the information to the actor
-waiting on the reply.
-It takes as an argument the actor sending the message, the message
-it is in reply to, and the rest of the arguments are the "body" of
-the message.
-(If an actor handles a message that is being "waited on" but does not
-explicitly reply to it, an auto-reply with an empty body will be
-triggered so that the waiting actor is not left waiting around.)
+[fn:class-bug]
+ #:class should be fine, except there is [[https://debbugs.gnu.org/cgi/bugreport.cgi?bug=25211][a bug in Guile]] which keeps
+ us from using it for now.
-The last thing to note is the call to "self-destruct".
-This does what you might expect: it removes the actor from the hive.
-No new messages will be sent to it.
-Ka-poof!
+[fn:actor-alive-deprecated-soon]
+ Or rather, for now you should call =actor-alive?= if your code
+ is looping like this.
+ In the future, after an actor dies, its coroutines will
+ automatically be "canceled".
** Writing our own network-enabled actor
(define-actor <telcmd> (<actor>)
((*init* telcmd-init)
(*cleanup* telcmd-cleanup)
- (new-client telcmd-new-client))
+ (new-client telcmd-new-client)
+ (handle-line telcmd-handle-line))
(socket #:accessor telcmd-socket
#:init-value #f))
#+END_SRC
(cond ((eof-object? line)
(close client))
(else
- (telcmd-handle-line telcmd client
- (string-trim-right line #\return))
+ (<- (actor-id telcmd) 'handle-line
+ client (string-trim-right line #\return))
(when (actor-alive? telcmd)
(loop)))))))
- (define (telcmd-handle-line telcmd client line)
+ (define (telcmd-handle-line telcmd message client line)
(match (string-split line #\space)
(("") #f) ; ignore empty lines
(("time" _ ...)
The actual method called whenever we have a "line" of input is pretty
straightforward... in fact it looks an awful lot like the IRC bot
handle-line procedure we used earlier.
-No surprises there!
+No surprises there![fn:why-send-a-message-to-handle-line]
Now let's run it:
finishes handling one message.
Our loop couldn't look quite like this though!
+[fn:why-send-a-message-to-handle-line]
+ Well, there may be one surprise to a careful observer.
+ Why are we sending a message to ourselves?
+ Couldn't we have just dropped the argument of "message" to
+ telcmd-handle-line and just called it like any other procedure?
+ Indeed, we /could/ do that, but sending a message to ourself has
+ an added advantage: if we accidentally "break" the
+ telcmd-handle-line procedure in some way (say we add a fun new
+ command we're playing with it), raising an exception won't break
+ and disconnect the client's main loop, it'll just break the
+ message handler for that one line, and our telcmd will happily
+ chug along accepting another command from the user while we try
+ to figure out what happened to the last one.
+
+** An intermission on live hacking
+
+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:
+
+#+BEGIN_SRC scheme
+ (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 '()))
+#+END_SRC
+
+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:
+
+#+BEGIN_SRC scheme
+ ;; From this:
+ ("botsnack"
+ (respond "Yippie! *does a dance!*"))
+
+ ;; To this:
+ ("botsnack"
+ (respond "Yippie! *catches botsnack in midair!*"))
+#+END_SRC
+
+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?
+
+#+BEGIN_SRC text
+IRC> /query examplebot
+<foo-user> examplebot: hi!
+#+END_SRC
+
+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:
+
+#+BEGIN_SRC scheme
+ (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 ...]
+ )
+#+END_SRC
+
+Re-evaluate.
+Now let's ping our bot in both the channel and over PM.
+
+#+BEGIN_SRC text
+;;; (channel "##botchat")
+
+;;; (channel "sinkbot")
+#+END_SRC
+
+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.)
+
+#+BEGIN_SRC scheme
+ (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 ...]
+ )
+#+END_SRC
+
+Re-evaluate and test.
+
+#+BEGIN_SRC text
+IRC> /query examplebot
+<foo-user> examplebot: hi!
+<examplebot> Oh hi foo-user!
+#+END_SRC
+
+Horray!
+
* API reference
actual theory!
8sync is based on the [[https://en.wikipedia.org/wiki/Actor_model][actor model]] whereas fibers follows
[[http://usingcsp.com/][Communicating Sequential Processes (CSP)]], which is a form of
-[[https://en.wikipedia.org/wiki/Process_calculus][process calculi]].
+[[https://en.wikipedia.org/wiki/Process_calculus][process calculi]].
And it turns out, the
[[https://en.wikipedia.org/wiki/Actor_model_and_process_calculi][relationship between the actor model and process calculi]] is well documented,
and even more precisely, the