(channels '("##botchat")))
(define hive (make-hive))
(define irc-bot
- (hive-create-actor* hive <my-irc-bot> "irc-bot"
- #:username username
- #:server server
- #:channels channels))
+ (bootstrap-actor* hive <my-irc-bot> "irc-bot"
+ #:username username
+ #:server server
+ #:channels channels))
(run-hive hive (list (bootstrap-message hive irc-bot 'init))))
#+END_SRC
Actors are connected to something called a "hive", which is a
special kind of actor that runs all the other actors.
Actors can spawn other actors, but before we start the hive we use
-this special "hive-create-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. hive-create-actor* passes back not the actor itself (we don't
+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
#+BEGIN_SRC scheme
(define-method (handle-line (irc-bot <my-irc-bot>) speaker channel
line emote?)
- (<- irc-bot (actor-id irc-bot) 'send-line channel
+ (<- (actor-id irc-bot) 'send-line channel
(format #f "Bawwwwk! ~a says: ~a" speaker line)))
#+END_SRC
(or (equal? str my-name)
(equal? str (string-concatenate (list my-name ":")))))
(when (looks-like-me?)
- (<- irc-bot (actor-id irc-bot) 'send-line channel
+ (<- (actor-id irc-bot) 'send-line channel
(format #f "Bawwwwk! ~a says: ~a" speaker line))))
#+END_SRC
(match action
;; The classic botsnack!
("botsnack"
- (<- irc-bot (actor-id irc-bot) 'send-line channel
+ (<- (actor-id irc-bot) 'send-line channel
"Yippie! *does a dance!*"))
;; Return greeting
((or "hello" "hello!" "hello." "greetings" "greetings." "greetings!"
"hei" "hei." "hei!" "hi" "hi!")
- (<- irc-bot (actor-id irc-bot) 'send-line channel
+ (<- (actor-id irc-bot) 'send-line channel
(format #f "Oh hi ~a!" speaker)))
("echo"
- (<- irc-bot (actor-id irc-bot) 'send-line channel
+ (<- (actor-id irc-bot) 'send-line channel
(string-join action-args " ")))
;; ---> Add yours here <---
;; Default
(_
- (<- irc-bot (actor-id irc-bot) 'send-line channel
+ (<- (actor-id irc-bot) 'send-line channel
"*stupid puppy look*"))))))
#+END_SRC
(or (equal? str my-name)
(equal? str (string-concatenate (list my-name ":")))))
(define (respond respond-line)
- (<- irc-bot (actor-id irc-bot) 'send-line channel
+ (<- (actor-id irc-bot) 'send-line channel
respond-line))
(match (string-split line #\space)
(((? looks-like-me? _) action action-args ...)
(repl-path "/tmp/8sync-repl"))
(define hive (make-hive))
(define irc-bot
- (hive-create-actor* hive <my-irc-bot> "irc-bot"
- #:username username
- #:server server
- #:channels channels))
+ (bootstrap-actor* hive <my-irc-bot> "irc-bot"
+ #:username username
+ #:server server
+ #:channels channels))
(define repl-manager
- (hive-create-actor* hive <repl-manager> "repl"
+ (bootstrap-actor* hive <repl-manager> "repl"
#:path repl-path))
(run-hive hive (list (bootstrap-message hive irc-bot 'init)
line emote?)
;; [... snip ...]
(define (respond respond-line)
- (<- irc-bot (actor-id irc-bot) 'send-line (pk 'channel channel)
+ (<- (actor-id irc-bot) 'send-line (pk 'channel channel)
respond-line))
;; [... snip ...]
)
line emote?)
;; [... snip ...]
(define (respond respond-line)
- (<- irc-bot (actor-id irc-bot) 'send-line
+ (<- (actor-id irc-bot) 'send-line
(if (looks-like-me? channel)
speaker ; PM session
channel) ; normal IRC channel
Horray!
-** Writing our own actors and sending messages between them
+** Writing our own actors
Let's write the most basic, boring actor possible.
How about an actor that start sleeping, and keeps sleeping?
(loop sleeper-loop))))
(define (sleeper-loop actor message)
- (while (actor-am-i-alive? actor)
+ (while (actor-alive? actor)
(display "Zzzzzzzz....\n")
;; Sleep for one second
(8sleep 1)))
(let* ((hive (make-hive))
- (sleeper (hive-create-actor hive <sleeper>)))
- (ez-run-hive hive (list (bootstrap-message hive sleeper 'loop))))
+ (sleeper (bootstrap-actor hive <sleeper>)))
+ (run-hive hive (list (bootstrap-message hive sleeper 'loop))))
#+END_SRC
We see some particular things in this example.
"8sleep" is like Guile's "sleep" method, except it is non-blocking
and will always yield to the scheduler.
-Our while loop also checks "actor-am-i-alive?" to see whether or not
+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.
(define (manager-assign-task manager message difficulty)
"Delegate a task to our direct report"
(display "manager> Work on this task for me!\n")
- (<- manager (manager-direct-report manager)
+ (<- (manager-direct-report manager)
'work-on-this difficulty))
#+END_SRC
(work-on-this worker-work-on-this))))
(define (worker-work-on-this worker message difficulty)
- ""
+ "Work on one task until done."
(set! (worker-task-left worker) difficulty)
(display "worker> Whatever you say, boss!\n")
- (while (and (actor-am-i-alive? worker)
+ (while (and (actor-alive? worker)
(> (worker-task-left worker) 0))
(display "worker> *huff puff*\n")
(set! (worker-task-left worker)
(- (worker-task-left worker) 1))
- (8sync (/ 1 3)))
- (display "worker> Looks like I'm done! Can I go home yet?\n"))
+ (8sleep (/ 1 3))))
#+END_SRC
The worker also contains familiar code, but we now see that we can
-call 8sync with non-integer real numbers.
+call 8sleep with non-integer real numbers.
Looks like there's nothing left to do but run it:
#+BEGIN_SRC scheme
(let* ((hive (make-hive))
- (worker (hive-create-actor hive <worker>))
- (manager (hive-create-actor hive <manager>
- #:direct-report worker)))
- (ez-run-hive hive (list (bootstrap-message hive manager 'assign-task 5))))
+ (worker (bootstrap-actor hive <worker>))
+ (manager (bootstrap-actor hive <manager>
+ #:direct-report worker)))
+ (run-hive hive (list (bootstrap-message hive manager 'assign-task 5))))
#+END_SRC
#+BEGIN_SRC text
worker> *huff puff*
worker> *huff puff*
worker> *huff puff*
-worker> Looks like I'm done! Can I go home yet?
#+END_SRC
"<-" pays no attention to what happens with the messages it has sent
(define (manager-assign-task manager message difficulty)
"Delegate a task to our direct report"
(display "manager> Work on this task for me!\n")
- (<- manager (manager-direct-report manager)
+ (<- (manager-direct-report manager)
'work-on-this difficulty)
- ;; call the micromanagement loop
+ ;; Wait a moment, then call the micromanagement loop
+ (8sleep (/ 1 2))
(manager-micromanage-loop manager))
;;; And add the following
(define (manager-micromanage-loop manager)
"Pester direct report until they're done with their task."
(display "manager> Are you done yet???\n")
- (let ((still-working
- (msg-val (<-wait manager (manager-direct-report manager)
- 'done-yet?))))
- (if still-working
- (begin (display "manager> Harumph!\n")
- (8sleep 1)
- (when (actor-am-i-alive? manager)
- (manager-micromanage-loop manager)))
+ (let ((worker-is-done
+ (mbody-val (<-wait (manager-direct-report manager)
+ 'done-yet?))))
+ (if worker-is-done
(begin (display "manager> Oh! I guess you can go home then.\n")
- (<- manager (manager-direct-report manager) 'go-home)))))
+ (<- (manager-direct-report manager) 'go-home))
+ (begin (display "manager> Harumph!\n")
+ (8sleep (/ 1 2))
+ (when (actor-alive? manager)
+ (manager-micromanage-loop manager))))))
#+END_SRC
We've appended a micromanagement loop here... but what's going on?
"<-wait", as it sounds, waits for a reply, and returns a reply
message.
In this case there's a value in the body of the message we want,
-so we pull it out with msg-val.
+so we pull it out with mbody-val.
(It's possible for a remote actor to return multiple values, in which
-case we'd want to use msg-receive, but that's a bit more complicated.)
+case we'd want to use mbody-receive, but that's a bit more
+complicated.)
Of course, we need to update our worker accordingly as well.
;;; New procedures:
(define (worker-done-yet? worker message)
"Reply with whether or not we're done yet."
- (<-reply worker message
- (= (worker-task-left worker) 0)))
+ (let ((am-i-done? (= (worker-task-left worker) 0)))
+ (if am-i-done?
+ (display "worker> Yes, I finished up!\n")
+ (display "worker> No... I'm still working on it...\n"))
+ (<-reply message am-i-done?)))
(define (worker-go-home worker message)
"It's off of work for us!"
- (display "worker> Whew! Free at last.")
+ (display "worker> Whew! Free at last.\n")
(self-destruct worker))
#+END_SRC
+(As you've probably guessed, you wouldn't normally call =display=
+everywhere as we are in this program... that's just to make the
+examples more illustrative.)
+
+Running it is the same as before:
+
+#+BEGIN_SRC scheme
+ (let* ((hive (make-hive))
+ (worker (bootstrap-actor hive <worker>))
+ (manager (bootstrap-actor hive <manager>
+ #:direct-report worker)))
+ (run-hive hive (list (bootstrap-message hive manager 'assign-task 5))))
+#+END_SRC
+
+But the output is a bit different:
+
+#+BEGIN_SRC scheme
+manager> Work on this task for me!
+worker> Whatever you say, boss!
+worker> *huff puff*
+worker> *huff puff*
+manager> Are you done yet???
+worker> No... I'm still working on it...
+manager> Harumph!
+worker> *huff puff*
+manager> Are you done yet???
+worker> *huff puff*
+worker> No... I'm still working on it...
+manager> Harumph!
+worker> *huff puff*
+manager> Are you done yet???
+worker> Yes, I finished up!
+manager> Oh! I guess you can go home then.
+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
No new messages will be sent to it.
Ka-poof!
-** Extended example: an IRC bot battle
-
-** Adding a "rankings" web page
-
** Writing our own <irc-bot> from scratch
* API reference
+* Systems reference
+** IRC
+** Web / HTTP
+** COMMENT Websockets
+
+* Addendum
+** Recommended .emacs additions
+
+In order for =mbody-receive= to indent properly, put this in your
+.emacs:
+
+#+BEGIN_SRC emacs-lisp
+(put 'mbody-receive 'scheme-indent-function 2)
+#+END_SRC
+
+** 8sync and Fibers
+
+One other major library for asynchronous communication in Guile-land
+is [[https://github.com/wingo/fibers/][Fibers]].
+There's a lot of overlap:
+
+ - Both use Guile's suspendable-ports facility
+ - Both communicate between asynchronous processes using message passing;
+ you don't have to squint hard to see the relationship between Fibers'
+ channels and 8sync's actor inboxes.
+
+However, there are clearly differences too.
+There's a one to one relationship between 8sync actors and an actor inbox,
+whereas each Fibers fiber may read from multiple channels, for example.
+
+Luckily, it turns out there's a clear relationship, based on real,
+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]].
+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
+[[https://en.wikipedia.org/wiki/Communicating_sequential_processes#Comparison_with_the_Actor_Model][relationship between CSP and the actor model]] is well understood too.
+
+So, 8sync and Fibers do take somewhat different approaches, but both
+have a solid theoretical backing... and their theories are well
+understood in terms of each other.
+Good news for theory nerds!
+
+(Since the actors and CSP are [[https://en.wikipedia.org/wiki/Dual_%28mathematics%29][dual]], maybe eventually 8sync will be
+implemented on top of Fibers... that remains to be seen!)
+