X-Git-Url: https://jxself.org/git/?a=blobdiff_plain;f=doc%2F8sync-new-manual.org;h=c4cf67ef337e0f19c56e99a289db0a8772db60ec;hb=68472a819457ce0e6053b59b2b9a1657669e8801;hp=1caa79b8ccb15adfa5e52c4c46c17b7a9ee5ddae;hpb=39be3a1d4f667eae7dc31b7677721692aab3a319;p=8sync.git diff --git a/doc/8sync-new-manual.org b/doc/8sync-new-manual.org index 1caa79b..c4cf67e 100644 --- a/doc/8sync-new-manual.org +++ b/doc/8sync-new-manual.org @@ -161,22 +161,22 @@ yet. Time to fix that! (channels '("##botchat"))) (define hive (make-hive)) (define irc-bot - (hive-create-actor* hive "irc-bot" - #:username username - #:server server - #:channels channels)) - (run-hive hive (list (bootstrap-message hive irc-bot 'init)))) + (bootstrap-actor* hive "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 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 @@ -205,7 +205,7 @@ Change handle-line to this: #+BEGIN_SRC scheme (define-method (handle-line (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 @@ -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?) - (<- 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 @@ -327,22 +327,22 @@ To implement it, we're going to pull out Guile's pattern matcher. (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 @@ -360,7 +360,7 @@ you're right: (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 ...) @@ -426,16 +426,15 @@ Redefine run-bot like so: (repl-path "/tmp/8sync-repl")) (define hive (make-hive)) (define irc-bot - (hive-create-actor* hive "irc-bot" - #:username username - #:server server - #:channels channels)) + (bootstrap-actor* hive "irc-bot" + #:username username + #:server server + #:channels channels)) (define repl-manager - (hive-create-actor* hive "repl" + (bootstrap-actor* hive "repl" #:path repl-path)) - (run-hive hive (list (bootstrap-message hive irc-bot 'init) - (bootstrap-message hive repl-manager 'init)))) + (run-hive hive '())) #+END_SRC If we put a call to run-bot at the bottom of our file we can call it, @@ -503,7 +502,7 @@ things to: 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 ...] ) @@ -533,7 +532,7 @@ to looks like our own username that we respond back to the sender. 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 @@ -552,7 +551,7 @@ IRC> /query examplebot 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? @@ -567,13 +566,13 @@ 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 (bootstrap-actor hive ))) (run-hive hive (list (bootstrap-message hive sleeper 'loop)))) #+END_SRC @@ -588,7 +587,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. -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. @@ -615,7 +614,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") - (<- manager (manager-direct-report manager) + (<- (manager-direct-report manager) 'work-on-this difficulty)) #+END_SRC @@ -642,28 +641,27 @@ reference other actors. (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 )) - (manager (hive-create-actor hive - #:direct-report worker))) + (worker (bootstrap-actor hive )) + (manager (bootstrap-actor hive + #:direct-report worker))) (run-hive hive (list (bootstrap-message hive manager 'assign-task 5)))) #+END_SRC @@ -675,7 +673,6 @@ 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 @@ -698,10 +695,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") - (<- 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 @@ -709,25 +707,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") - (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. @@ -746,15 +745,54 @@ 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 )) + (manager (bootstrap-actor hive + #: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 @@ -777,3 +815,47 @@ Ka-poof! ** 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!) +