doc: Tweak and fix the "Writing our own actors" section.
[8sync.git] / doc / 8sync-new-manual.org
index e85b639b5be2f9a4ef2e0740ac89deabe8ecb5e2..d2771c1108f627ef74234dda061663f9c94c30f9 100644 (file)
@@ -161,22 +161,22 @@ yet.  Time to fix that!
                   (channels '("##botchat")))
   (define hive (make-hive))
   (define irc-bot
                   (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
   (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
 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
 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
@@ -205,7 +205,7 @@ Change handle-line to this:
 #+BEGIN_SRC scheme
   (define-method (handle-line (irc-bot <my-irc-bot>) speaker channel
                               line emote?)
 #+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
 
         (format #f "Bawwwwk! ~a says: ~a" speaker line)))
 #+END_SRC
 
@@ -295,7 +295,7 @@ Luckily this is an easy adjustment to make.
       (or (equal? str my-name)
           (equal? str (string-concatenate (list my-name ":")))))
     (when (looks-like-me?)
       (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
 
           (format #f "Bawwwwk! ~a says: ~a" speaker line))))
 #+END_SRC
 
@@ -327,22 +327,22 @@ To implement it, we're going to pull out Guile's pattern matcher.
        (match action
          ;; The classic botsnack!
          ("botsnack"
        (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!")
               "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"
               (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
          (_
               (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
 
               "*stupid puppy look*"))))))
 #+END_SRC
 
@@ -360,7 +360,7 @@ you're right:
       (or (equal? str my-name)
           (equal? str (string-concatenate (list my-name ":")))))
     (define (respond respond-line)
       (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 ...)
           respond-line))
     (match (string-split line #\space)
       (((? looks-like-me? _) action action-args ...)
@@ -426,12 +426,12 @@ Redefine run-bot like so:
                     (repl-path "/tmp/8sync-repl"))
     (define hive (make-hive))
     (define irc-bot
                     (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
     (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)
                           #:path repl-path))
 
     (run-hive hive (list (bootstrap-message hive irc-bot 'init)
@@ -503,7 +503,7 @@ things to:
                               line emote?)
     ;; [... snip ...]
     (define (respond respond-line)
                               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 ...]
     )
           respond-line))
     ;; [... snip ...]
     )
@@ -533,7 +533,7 @@ to looks like our own username that we respond back to the sender.
                               line emote?)
     ;; [... snip ...]
     (define (respond respond-line)
                               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
           (if (looks-like-me? channel)
               speaker    ; PM session
               channel)   ; normal IRC channel
@@ -552,7 +552,7 @@ IRC> /query examplebot
 
 Horray!
 
 
 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?
 
 Let's write the most basic, boring actor possible.
 How about an actor that start sleeping, and keeps sleeping?
@@ -567,14 +567,14 @@ How about an actor that start sleeping, and keeps sleeping?
                            (loop sleeper-loop))))
 
   (define (sleeper-loop actor message)
                            (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))
       (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.
 #+END_SRC
 
 We see some particular things in this example.
@@ -588,7 +588,7 @@ In our sleeper-loop we also see a call to "8sleep".
 "8sleep" is like Guile's "sleep" method, except it is non-blocking
 and will always yield to the scheduler.
 
 "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.
 it is still registered.
 In general, if you keep a loop in your actor that regularly yields
 to the scheduler, you should check this.
@@ -615,7 +615,7 @@ Time to get back to work!
   (define (manager-assign-task manager message difficulty)
     "Delegate a task to our direct report"
     (display "manager> Work on this task for me!\n")
   (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 difficulty))
 #+END_SRC
 
@@ -642,29 +642,28 @@ reference other actors.
                            (work-on-this worker-work-on-this))))
 
   (define (worker-work-on-this worker message difficulty)
                            (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")
     (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))
                 (> (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
 #+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))
 
 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
 #+END_SRC
 
 #+BEGIN_SRC text
@@ -675,7 +674,6 @@ worker> *huff puff*
 worker> *huff puff*
 worker> *huff puff*
 worker> *huff puff*
 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
 #+END_SRC
 
 "<-" pays no attention to what happens with the messages it has sent
@@ -698,10 +696,11 @@ into a micromanager.
   (define (manager-assign-task manager message difficulty)
     "Delegate a task to our direct report"
     (display "manager> Work on this task for me!\n")
   (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)
 
         '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
     (manager-micromanage-loop manager))
 
   ;;; And add the following
@@ -709,25 +708,26 @@ into a micromanager.
   (define (manager-micromanage-loop manager)
     "Pester direct report until they're done with their task."
     (display "manager> Are you done yet???\n")
   (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")
           (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,
 #+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
 (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.
 
 
 Of course, we need to update our worker accordingly as well.
 
@@ -746,15 +746,50 @@ 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."
   ;;; 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!"
 
   (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
 
     (self-destruct worker))
 #+END_SRC
 
+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
 "<-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
@@ -769,11 +804,55 @@ This does what you might expect: it removes the actor from the hive.
 No new messages will be sent to it.
 Ka-poof!
 
 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
 
 ** 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!)
+