doc: Rename calls to ez-run-hive to run-hive and adjust titles.
[8sync.git] / doc / 8sync-new-manual.org
index 51c670ed4764cf5262cfb6bf337463f51bbde37c..1caa79b8ccb15adfa5e52c4c46c17b7a9ee5ddae 100644 (file)
@@ -173,7 +173,7 @@ 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.
 It takes the hive as its first argument, the actor class as the second
 Actors can spawn other actors, but before we start the hive we use
 this special "hive-create-actor*" method.
 It takes the hive as its first argument, the actor class as the second
-argument, a decoraive "cookie" as the third argument (this is
+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
 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
@@ -343,14 +343,7 @@ To implement it, we're going to pull out Guile's pattern matcher.
          ;; Default
          (_
           (<- irc-bot (actor-id irc-bot) 'send-line channel
          ;; Default
          (_
           (<- irc-bot (actor-id irc-bot) 'send-line channel
-              "*stupid puppy look*"))))
-      ;; Otherwise... just spit the output to current-output-port or whatever
-      (_
-       (if emote?
-           (format #t "~a emoted ~s in channel ~a\n"
-                   speaker line channel)
-           (format #t "~a said ~s in channel ~a\n"
-                   speaker line channel)))))
+              "*stupid puppy look*"))))))
 #+END_SRC
 
 Parsing the pattern matcher syntax is left as an exercise for the
 #+END_SRC
 
 Parsing the pattern matcher syntax is left as an exercise for the
@@ -386,14 +379,7 @@ you're right:
 
          ;; Default
          (_
 
          ;; Default
          (_
-          (respond "*stupid puppy look*"))))
-      ;; Otherwise... just spit the output to current-output-port or whatever
-      (_
-       (if emote?
-           (format #t "~a emoted ~s in channel ~a\n"
-                   speaker line channel)
-           (format #t "~a said ~s in channel ~a\n"
-                   speaker line channel)))))
+          (respond "*stupid puppy look*"))))))
 #+END_SRC
 
 Okay, that looks pretty good!
 #+END_SRC
 
 Okay, that looks pretty good!
@@ -430,19 +416,364 @@ 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.
 
 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
+      (hive-create-actor* hive <my-irc-bot> "irc-bot"
+                          #:username username
+                          #:server server
+                          #:channels channels))
+    (define repl-manager
+      (hive-create-actor* hive <repl-manager> "repl"
+                          #:path repl-path))
+
+    (run-hive hive (list (bootstrap-message hive irc-bot 'init)
+                         (bootstrap-message hive repl-manager 'init))))
+#+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)
+      (<- irc-bot (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")
 
 
-# Finally, show off pk
+;;; (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)
+      (<- irc-bot (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!
+
+** Writing our own actors and sending messages between them
+
+Let's write the most basic, boring actor possible.
+How about an actor that start sleeping, and keeps sleeping?
+
+#+BEGIN_SRC scheme
+  (use-modules (oop goops)
+               (8sync))
+
+  (define-class <sleeper> (<actor>)
+    (actions #:allocation #:each-subclass
+             #:init-value (build-actions
+                           (loop sleeper-loop))))
+
+  (define (sleeper-loop actor message)
+    (while (actor-am-i-alive? actor)
+      (display "Zzzzzzzz....\n")
+      ;; Sleep for one second
+      (8sleep 1)))
+
+  (let* ((hive (make-hive))
+         (sleeper (hive-create-actor hive <sleeper>)))
+    (run-hive hive (list (bootstrap-message hive sleeper 'loop))))
+#+END_SRC
+
+We see some particular things in this example.
+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.)
+
+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.
+
+Our while loop also checks "actor-am-i-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.
+(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.
+If the actor was dead, the message simply would not be delivered and
+thus the loop would stop.)
+
+This actor is pretty lazy though.
+Time to get back to work!
+
+#+BEGIN_SRC scheme
+  (use-modules (8sync)
+               (oop goops))
+
+  (define-class <manager> (<actor>)
+    (direct-report #:init-keyword #:direct-report
+                   #:getter manager-direct-report)
+    (actions #:allocation #:each-subclass
+             #:init-value (build-actions
+                           (assign-task manager-assign-task))))
+
+  (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)
+        'work-on-this difficulty))
+#+END_SRC
+
+Here we're constructing a very simple manager actor.
+This manager keeps track of a direct report and tells them to start
+working on a task... simple delegation.
+Nothing here is really new, but note that our friend "<-" (which means
+"send message") is back.
+There's one difference this time... the first time we saw "<-" was in
+the handle-line procedure of the irc-bot, and in that case we explicitly
+pulled the actor-id after the actor we were sending the message to
+(ourselves), which we aren't doing here.
+But that was an unusual case, because the actor was ourself.
+In this case, and in general, actors don't have direct references to
+other actors; instead, all they have is access to identifiers which
+reference other actors.
+
+#+BEGIN_SRC scheme
+  (define-class <worker> (<actor>)
+    (task-left #:init-keyword #:task-left
+               #:accessor worker-task-left)
+    (actions #:allocation #:each-subclass
+             #:init-value (build-actions
+                           (work-on-this worker-work-on-this))))
+
+  (define (worker-work-on-this worker message difficulty)
+    ""
+    (set! (worker-task-left worker) difficulty)
+    (display "worker> Whatever you say, boss!\n")
+    (while (and (actor-am-i-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"))
+#+END_SRC
+
+The worker also contains familiar code, but we now see that we can
+call 8sync 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)))
+    (run-hive hive (list (bootstrap-message hive manager 'assign-task 5))))
+#+END_SRC
+
+#+BEGIN_SRC text
+manager> Work on this task for me!
+worker> Whatever you say, boss!
+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
+off.
+This is useful in many cases... we can blast off many messages and
+continue along without holding anything back.
+
+But sometimes we want to make sure that something completes before
+we do something else, or we want to send a message and get some sort
+of information back.
+Luckily 8sync comes with an answer to that with "<-wait", which will
+suspend the caller until the callee gives some sort of response, but
+which does not block the rest of the program from running.
+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)
+    "Delegate a task to our direct report"
+    (display "manager> Work on this task for me!\n")
+    (<- manager (manager-direct-report manager)
+        'work-on-this difficulty)
+
+    ;; call the micromanagement loop
+    (manager-micromanage-loop manager))
+
+  ;;; And add the following
+  ;;;   (... Note: do not model actual employee management off this)
+  (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)))
+          (begin (display "manager> Oh!  I guess you can go home then.\n")
+                 (<- manager (manager-direct-report manager) 'go-home)))))
+#+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.
+(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.)
+
+Of course, we need to update our worker accordingly as well.
+
+#+BEGIN_SRC scheme
+  ;;; Update the worker to add the following new actions:
+  (define-class <worker> (<actor>)
+    (task-left #:init-keyword #:task-left
+               #:accessor worker-task-left)
+    (actions #:allocation #:each-subclass
+             #:init-value (build-actions
+                           (work-on-this worker-work-on-this)
+                           ;; Add these:
+                           (done-yet? worker-done-yet?)
+                           (go-home worker-go-home))))
+
+  ;;; 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)))
+
+  (define (worker-go-home worker message)
+    "It's off of work for us!"
+    (display "worker> Whew!  Free at last.")
+    (self-destruct worker))
+#+END_SRC
 
 
-** Battle bot!
+"<-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.)
 
 
-** Adding a "rankings" web page
+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!
 
 ** 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