irc: Convert handle-line to a message handler.
[8sync.git] / doc / 8sync-new-manual.org
1 # Permission is granted to copy, distribute and/or modify this document
2 # under the terms of the GNU Free Documentation License, Version 1.3
3 # or any later version published by the Free Software Foundation;
4 # with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
5 # A copy of the license is included in the section entitled ``GNU
6 # Free Documentation License''.
7
8 # A copy of the license is also available from the Free Software
9 # Foundation Web site at http://www.gnu.org/licenses/fdl.html
10
11 # Altenately, this document is also available under the Lesser General
12 # Public License, version 3 or later, as published by the Free Software
13 # Foundation.
14
15 # A copy of the license is also available from the Free Software
16 # Foundation Web site at http://www.gnu.org/licenses/lgpl.html
17
18 * Preface
19
20 Welcome to 8sync's documentation!
21 8sync is an asynchronous programming environment for GNU Guile.
22 (Get it? 8sync? Async??? Quiet your groans, it's a great name!)
23
24 8sync has some nice properties:
25
26  - 8sync uses the actor model as its fundamental concurrency
27    synchronization mechanism.
28    Since the actor model is a "shared nothing" asynchronous
29    environment, you don't need to worry about deadlocks or other
30    tricky problems common to other asynchronous models.
31    Actors are modular units of code and state which communicate
32    by sending messages to each other.
33  - If you've done enough asynchronous programming, you're probably
34    familiar with the dreaded term "callback hell".
35    Getting around callback hell usually involves a tradeoff of other,
36    still rather difficult to wrap your brain around programming
37    patterns.
38    8sync uses some clever tricks involving "delimited continuations"
39    under the hood to make the code you write look familiar and
40    straightforward.
41    When you need to send a request to another actor and get some
42    information back from it without blocking, there's no need
43    to write a separate procedure... 8sync's scheduler will suspend
44    your procedure and wake it back up when a response is ready.
45  - Even nonblocking I/O code is straightforward to write.
46    Thanks to the "suspendable ports" code introduced in Guile 2.2,
47    writing asynchronous, nonblocking networked code looks mostly
48    like writing the same synchronous code.
49    8sync's scheduler handles suspending and resuming networked
50    code that would otherwise block.
51  - 8sync aims to be "batteries included".
52    Useful subsystems for IRC bots, HTTP servers, and so on are
53    included out of the box.
54  - 8sync prioritizes live hacking.
55    If using an editor like Emacs with a nice mode like Geiser,
56    an 8sync-using developer can change and fine-tune the behavior
57    of code /while it runs/.
58    This makes both debugging and development much more natural,
59    allowing the right designs to evolve under your fingertips.
60    A productive hacker is a happy hacker, after all!
61
62 In the future, 8sync will also provide the ability to spawn and
63 communicate with actors on different threads, processes, and machines,
64 with most code running the same as if actors were running in the same
65 execution environment.
66
67 But as a caution, 8sync is still very young.
68 The API is stabilizing, but not yet stable, and it is not yet well
69 "battle-tested".
70 Hacker beware!
71 But, consider this as much an opportunity as a warning.
72 8sync is in a state where there is much room for feedback and
73 contributions.
74 Your help wanted!
75
76 And now, into the wild, beautiful frontier.
77 Onward!
78
79 * Tutorial
80
81 ** A silly little IRC bot
82
83 IRC!  Internet Relay Chat!
84 The classic chat protocol of the Internet.
85 And it turns out, one of the best places to learn about networked
86 programming.[fn:irc-hacking]
87 We ourselves are going to explore chat bots as a basis for getting our
88 feet wet in 8sync.
89
90 First of all, we're going to need to import some modules.  Put this at
91 the top of your file:
92
93 #+BEGIN_SRC scheme
94   (use-modules (8sync)               ; 8sync's agenda and actors
95                (8sync systems irc)   ; the irc bot subsystem
96                (oop goops)           ; 8sync's actors use GOOPS
97                (ice-9 format)        ; basic string formatting
98                (ice-9 match))        ; pattern matching
99 #+END_SRC
100
101 Now we need to add our bot.  Initially, it won't do much.
102
103 #+BEGIN_SRC scheme
104   (define-class <my-irc-bot> (<irc-bot>))
105
106   (define-method (handle-line (irc-bot <my-irc-bot>) message
107                               speaker channel line emote?)
108     (if emote?
109         (format #t "~a emoted ~s in channel ~a\n"
110                 speaker line channel)
111         (format #t "~a said ~s in channel ~a\n"
112                 speaker line channel)))
113 #+END_SRC
114
115 We've just defined our own IRC bot!
116 This is an 8sync actor.
117 (8sync uses GOOPS to define actors.)
118 We extended the handle-line generic method, so this is the code that
119 will be called whenever the IRC bot "hears" anything.
120 This method is itself an action handler, hence the second argument
121 for =message=, which we can ignore for now.
122 Pleasantly, the message's argument body is passed in as the rest of
123 the arguments.
124
125 For now the code is pretty basic: it just outputs whatever it "hears"
126 from a user in a channel to the current output port.
127 Pretty boring!
128 But it should help us make sure we have things working when we kick
129 things off.
130
131 Speaking of, even though we've defined our actor, it's not running
132 yet.  Time to fix that!
133
134 #+BEGIN_SRC scheme
135 (define* (run-bot #:key (username "examplebot")
136                   (server "irc.freenode.net")
137                   (channels '("##botchat")))
138   (define hive (make-hive))
139   (define irc-bot
140     (bootstrap-actor* hive <my-irc-bot> "irc-bot"
141                       #:username username
142                       #:server server
143                       #:channels channels))
144   (run-hive hive '()))
145 #+END_SRC
146
147 Actors are connected to something called a "hive", which is a
148 special kind of actor that runs and manages all the other actors.
149 Actors can spawn other actors, but before we start the hive we use
150 this special "bootstrap-actor*" method.
151 It takes the hive as its first argument, the actor class as the second
152 argument, a decorative "cookie" as the third argument (this is
153 optional, but it helps with debugging... you can skip it by setting it
154 to #f if you prefer), and the rest are initialization arguments to the
155 actor.  bootstrap-actor* passes back not the actor itself (we don't
156 get access to that usually) but the *id* of the actor.
157 (More on this later.)
158 Finally we run the hive with run-hive and pass it a list of
159 "bootstrapped" messages.
160 Normally actors send messages to each other (and sometimes themselves),
161 but we need to send a message or messages to start things or else
162 nothing is going to happen.
163
164 We can run it like:
165
166 #+BEGIN_SRC scheme
167 (run-bot #:username "some-bot-name") ; be creative!
168 #+END_SRC
169
170 Assuming all the tubes on the internet are properly connected, you
171 should be able to join the "##botchat" channel on irc.freenode.net and
172 see your bot join as well.
173 Now, as you probably guessed, you can't really /do/ much yet.
174 If you talk to the bot, it'll send messages to the terminal informing
175 you as such, but it's hardly a chat bot if it's not chatting yet.
176
177 So let's do the most boring (and annoying) thing possible.
178 Let's get it to echo whatever we say back to us.
179 Change handle-line to this:
180
181 #+BEGIN_SRC scheme
182   (define-method (handle-line (irc-bot <my-irc-bot>) message
183                               speaker channel line emote?)
184     (<- (actor-id irc-bot) 'send-line channel
185         (format #f "Bawwwwk! ~a says: ~a" speaker line)))
186 #+END_SRC
187
188 This will do exactly what it looks like: repeat back whatever anyone
189 says like an obnoxious parrot.
190 Give it a try, but don't keep it running for too long... this
191 bot is so annoying it's likely to get banned from whatever channel
192 you put it in.
193
194 This method handler does have the advantage of being simple though.
195 It introduces a new concept simply... sending a message!
196 Whenever you see "<-", you can think of that as saying "send this
197 message".
198 The arguments to "<-" are as follows: the actor sending the message,
199 the id of the actor the message is being sent to, the "action" we
200 want to invoke (a symbol), and the rest are arguments to the
201 "action handler" which is in this case send-line (with itself takes
202 two arguments: the channel our bot should send a message to, and
203 the line we want it to spit out to the channel).
204
205 (Footnote: 8sync's name for sending a message, "<-", comes from older,
206 early lisp object oriented systems which were, as it turned out,
207 inspired by the actor model!
208 Eventually message passing was dropped in favor of something called
209 "generic functions" or "generic methods"
210 (you may observe we made use of such a thing in extending
211 handle-line).
212 Many lispers believe that there is no need for message passing
213 with generic methods and some advanced functional techniques,
214 but in a concurrent environment message passing becomes useful
215 again, especially when the communicating objects / actors are not
216 in the same address space.)
217
218 Normally in the actor model, we don't have direct references to
219 an actor, only an identifier.
220 This is for two reasons: to quasi-enforce the "shared nothing"
221 environment (actors absolutely control their own resources, and
222 "all you can do is send a message" to request that they modify
223 them) and because... well, you don't even know where that actor is!
224 Actors can be anything, and anywhere.
225 It's possible in 8sync to have an actor on a remote hive, which means
226 the actor could be on a remote process or even remote machine, and
227 in most cases message passing will look exactly the same.
228 (There are some exceptions; it's possible for two actors on the same
229 hive to "hand off" some special types of data that can't be serialized
230 across processes or the network, eg a socket or a closure, perhaps even
231 one with mutable state.
232 This must be done with care, and the actors should be careful both
233 to ensure that they are both local and that the actor handing things
234 off no longer accesses that value to preserve the actor model.
235 But this is an advanced topic, and we are getting ahead of ourselves.)
236 We have to supply the id of the receiving actor, and usually we'd have
237 only the identifier.
238 But since in this case, since the actor we're sending this to is
239 ourselves, we have to pass in our identifier, since the Hive won't
240 deliver to anything other than an address.
241
242 Astute readers may observe, since this is a case where we are just
243 referencing our own object, couldn't we just call "sending a line"
244 as a method of our own object without all the message passing?
245 Indeed, we do have such a method, so we /could/ rewrite handle-line
246 like so:
247
248 #+BEGIN_SRC scheme
249   (define-method (handle-line (irc-bot <my-irc-bot>) message
250                               speaker channel line emote?)
251     (irc-bot-send-line irc-bot channel
252                        (format #f "Bawwwwk! ~a says: ~a" speaker line)))
253 #+END_SRC
254
255 ... but we want to get you comfortable and familiar with message
256 passing, and we'll be making use of this same message passing shortly
257 so that /other/ actors may participate in communicating with IRC
258 through our IRC bot.
259
260 Anyway, our current message handler is simply too annoying.
261 What we would really like to do is have our bot respond to individual
262 "commands" like this:
263
264 #+BEGIN_SRC text
265   <foo-user> examplebot: hi!
266   <examplebot> Oh hi foo-user!
267   <foo-user> examplebot: botsnack
268   <examplebot> Yippie! *does a dance!*
269   <foo-user> examplebot: echo I'm a very silly bot
270   <examplebot> I'm a very silly bot
271 #+END_SRC
272
273 Whee, that looks like fun!
274 To implement it, we're going to pull out Guile's pattern matcher.
275
276 #+BEGIN_SRC scheme
277   (define-method (handle-line (irc-bot <my-irc-bot>) message
278                               speaker channel line emote?)
279     (define my-name (irc-bot-username irc-bot))
280     (define (looks-like-me? str)
281       (or (equal? str my-name)
282           (equal? str (string-concatenate (list my-name ":")))))
283     (match (string-split line #\space)
284       (((? looks-like-me? _) action action-args ...)
285        (match action
286          ;; The classic botsnack!
287          ("botsnack"
288           (<- (actor-id irc-bot) 'send-line channel
289               "Yippie! *does a dance!*"))
290          ;; Return greeting
291          ((or "hello" "hello!" "hello." "greetings" "greetings." "greetings!"
292               "hei" "hei." "hei!" "hi" "hi!")
293           (<- (actor-id irc-bot) 'send-line channel
294               (format #f "Oh hi ~a!" speaker)))
295          ("echo"
296           (<- (actor-id irc-bot) 'send-line channel
297               (string-join action-args " ")))
298
299          ;; --->  Add yours here <---
300
301          ;; Default
302          (_
303           (<- (actor-id irc-bot) 'send-line channel
304               "*stupid puppy look*"))))))
305 #+END_SRC
306
307 Parsing the pattern matcher syntax is left as an exercise for the
308 reader.
309
310 If you're getting the sense that we could make this a bit less wordy,
311 you're right:
312
313 #+BEGIN_SRC scheme
314   (define-method (handle-line (irc-bot <my-irc-bot>) message
315                               speaker channel line emote?)
316     (define my-name (irc-bot-username irc-bot))
317     (define (looks-like-me? str)
318       (or (equal? str my-name)
319           (equal? str (string-concatenate (list my-name ":")))))
320     (define (respond respond-line)
321       (<- (actor-id irc-bot) 'send-line channel
322           respond-line))
323     (match (string-split line #\space)
324       (((? looks-like-me? _) action action-args ...)
325        (match action
326          ;; The classic botsnack!
327          ("botsnack"
328           (respond "Yippie! *does a dance!*"))
329          ;; Return greeting
330          ((or "hello" "hello!" "hello." "greetings" "greetings." "greetings!"
331               "hei" "hei." "hei!" "hi" "hi." "hi!")
332           (respond (format #f "Oh hi ~a!" speaker)))
333          ("echo"
334           (respond (string-join action-args " ")))
335
336          ;; --->  Add yours here <---
337
338          ;; Default
339          (_
340           (respond "*stupid puppy look*"))))))
341 #+END_SRC
342
343 Okay, that looks pretty good!
344 Now we have enough information to build an IRC bot that can do a lot
345 of things.
346 Take some time to experiment with extending the bot a bit before
347 moving on to the next section!
348 What cool commands can you add?
349
350 [fn:irc-hacking]
351   In the 1990s I remember stumbling into some funky IRC chat rooms and
352   being astounded that people there had what they called "bots" hanging
353   around.
354   From then until now, I've always enjoyed encountering bots whose range
355   of functionality has spanned from saying absurd things, to taking
356   messages when their "owners" were offline, to reporting the weather,
357   to logging meetings for participants.
358   And it turns out, IRC bots are a great way to cut your teeth on
359   networked programming; since IRC is a fairly simple line-delineated
360   protocol, it's a great way to learn to interact with sockets.
361   (My first IRC bot helped my team pick a place to go to lunch, previously
362   a source of significant dispute!)
363   At the time of writing, venture capital awash startups are trying to
364   turn chatbots into "big business"... a strange (and perhaps absurd)
365   thing given chat bots being a fairly mundane novelty amongst hackers
366   and teenagers everywhere a few decades ago.
367
368 ** Writing our own actors
369
370 Let's write the most basic, boring actor possible.
371 How about an actor that start sleeping, and keeps sleeping?
372
373 #+BEGIN_SRC scheme
374   (use-modules (oop goops)
375                (8sync))
376
377   (define-class <sleeper> (<actor>)
378     (actions #:allocation #:each-subclass
379              #:init-value (build-actions
380                            (*init* sleeper-loop))))
381
382   (define (sleeper-loop actor message)
383     (while (actor-alive? actor)
384       (display "Zzzzzzzz....\n")
385       ;; Sleep for one second      
386       (8sleep (sleeper-sleep-secs actor))))
387
388   (let* ((hive (make-hive))
389          (sleeper (bootstrap-actor hive <sleeper>)))
390     (run-hive hive '()))
391 #+END_SRC
392
393 We see some particular things in this example.
394 One thing is that our <sleeper> actor has an actions slot.
395 This is used to look up what the "action handler" for a message is.
396 We have to set the #:allocation to either #:each-subclass or #:class.
397 (#:class should be fine, except there is [[https://debbugs.gnu.org/cgi/bugreport.cgi?bug=25211][a bug in Guile]] which keeps
398 us from using it for now.)
399
400 The only action handler we've added is for =*init*=, which is called
401 implicitly when the actor first starts up.
402 (This will be true whether we bootstrap the actor before the hive
403 starts or create it during the hive's execution.)
404
405 In our sleeper-loop we also see a call to "8sleep".
406 "8sleep" is like Guile's "sleep" method, except it is non-blocking
407 and will always yield to the scheduler.
408
409 Our while loop also checks "actor-alive?" to see whether or not
410 it is still registered.
411 In general, if you keep a loop in your actor that regularly yields
412 to the scheduler, you should check this.
413 (An alternate way to handle it would be to not use a while loop at all
414 but simply send a message to ourselves with "<-" to call the
415 sleeper-loop handler again.
416 If the actor was dead, the message simply would not be delivered and
417 thus the loop would stop.)
418
419 It turns out we could have written the class for the actor much more
420 simply:
421
422 #+BEGIN_SRC scheme
423   ;; You could do this instead of the define-class above.
424   (define-actor <sleeper> (<actor>)
425     ((*init* sleeper-loop)))
426 #+END_SRC
427
428 This is sugar, and expands into exactly the same thing as the
429 define-class above.
430 The third argument is an argument list, the same as what's passed
431 into build-actions.
432 Everything after that is a slot.
433 So for example, if we had added an optional slot to specify
434 how many seconds to sleep, we could have done it like so:
435
436 #+BEGIN_SRC scheme
437   (define-actor <sleeper> (<actor>)
438     ((*init* sleeper-loop))
439     (sleep-secs #:init-value 1
440                 #:getter sleeper-sleep-secs))
441 #+END_SRC
442
443 This actor is pretty lazy though.
444 Time to get back to work!
445 Let's build a worker / manager type system.
446
447 #+BEGIN_SRC scheme
448   (use-modules (8sync)
449                (oop goops))
450
451   (define-actor <manager> (<actor>)
452     ((assign-task manager-assign-task))
453     (direct-report #:init-keyword #:direct-report
454                    #:getter manager-direct-report))
455
456   (define (manager-assign-task manager message difficulty)
457     "Delegate a task to our direct report"
458     (display "manager> Work on this task for me!\n")
459     (<- (manager-direct-report manager)
460         'work-on-this difficulty))
461 #+END_SRC
462
463 This manager keeps track of a direct report and tells them to start
464 working on a task... simple delegation.
465 Nothing here is really new, but note that our friend "<-" (which means
466 "send message") is back.
467 There's one difference this time... the first time we saw "<-" was in
468 the handle-line procedure of the irc-bot, and in that case we explicitly
469 pulled the actor-id after the actor we were sending the message to
470 (ourselves), which we aren't doing here.
471 But that was an unusual case, because the actor was ourself.
472 In this case, and in general, actors don't have direct references to
473 other actors; instead, all they have is access to identifiers which
474 reference other actors.
475
476 #+BEGIN_SRC scheme
477   (define-actor <worker> (<actor>)
478     ((work-on-this worker-work-on-this))
479     (task-left #:init-keyword #:task-left
480                #:accessor worker-task-left))
481
482   (define (worker-work-on-this worker message difficulty)
483     "Work on one task until done."
484     (set! (worker-task-left worker) difficulty)
485     (display "worker> Whatever you say, boss!\n")
486     (while (and (actor-alive? worker)
487                 (> (worker-task-left worker) 0))
488       (display "worker> *huff puff*\n")
489       (set! (worker-task-left worker)
490             (- (worker-task-left worker) 1))
491       (8sleep (/ 1 3))))
492 #+END_SRC
493
494 The worker also contains familiar code, but we now see that we can
495 call 8sleep with non-integer real numbers.
496
497 Looks like there's nothing left to do but run it.
498
499 #+BEGIN_SRC scheme
500   (let* ((hive (make-hive))
501          (worker (bootstrap-actor hive <worker>))
502          (manager (bootstrap-actor hive <manager>
503                                    #:direct-report worker)))
504     (run-hive hive (list (bootstrap-message hive manager 'assign-task 5))))
505 #+END_SRC
506
507 Unlike the =<sleeper>=, our =<manager>= doesn't have an implicit
508 =*init*= method, so we've bootstrapped the calling =assign-task= action.
509
510 #+BEGIN_SRC text
511 manager> Work on this task for me!
512 worker> Whatever you say, boss!
513 worker> *huff puff*
514 worker> *huff puff*
515 worker> *huff puff*
516 worker> *huff puff*
517 worker> *huff puff*
518 #+END_SRC
519
520 "<-" pays no attention to what happens with the messages it has sent
521 off.
522 This is useful in many cases... we can blast off many messages and
523 continue along without holding anything back.
524
525 But sometimes we want to make sure that something completes before
526 we do something else, or we want to send a message and get some sort
527 of information back.
528 Luckily 8sync comes with an answer to that with "<-wait", which will
529 suspend the caller until the callee gives some sort of response, but
530 which does not block the rest of the program from running.
531 Let's try applying that to our own code by turning our manager
532 into a micromanager.
533
534 #+BEGIN_SRC scheme
535   ;;; Update this method
536   (define (manager-assign-task manager message difficulty)
537     "Delegate a task to our direct report"
538     (display "manager> Work on this task for me!\n")
539     (<- (manager-direct-report manager)
540         'work-on-this difficulty)
541
542     ;; Wait a moment, then call the micromanagement loop
543     (8sleep (/ 1 2))
544     (manager-micromanage-loop manager))
545
546   ;;; And add the following
547   ;;;   (... Note: do not model actual employee management off this)
548   (define (manager-micromanage-loop manager)
549     "Pester direct report until they're done with their task."
550     (display "manager> Are you done yet???\n")
551     (let ((worker-is-done
552            (mbody-val (<-wait (manager-direct-report manager)
553                               'done-yet?))))
554       (if worker-is-done
555           (begin (display "manager> Oh!  I guess you can go home then.\n")
556                  (<- (manager-direct-report manager) 'go-home))
557           (begin (display "manager> Harumph!\n")
558                  (8sleep (/ 1 2))
559                  (when (actor-alive? manager)
560                    (manager-micromanage-loop manager))))))
561 #+END_SRC
562
563 We've appended a micromanagement loop here... but what's going on?
564 "<-wait", as it sounds, waits for a reply, and returns a reply
565 message.
566 In this case there's a value in the body of the message we want,
567 so we pull it out with mbody-val.
568 (It's possible for a remote actor to return multiple values, in which
569 case we'd want to use mbody-receive, but that's a bit more
570 complicated.)
571
572 Of course, we need to update our worker accordingly as well.
573
574 #+BEGIN_SRC scheme
575   ;;; Update the worker to add the following new actions:
576   (define-actor <worker> (<actor>)
577     ((work-on-this worker-work-on-this)
578      ;; Add these:
579      (done-yet? worker-done-yet?)
580      (go-home worker-go-home))
581     (task-left #:init-keyword #:task-left
582                #:accessor worker-task-left))
583
584   ;;; New procedures:
585   (define (worker-done-yet? worker message)
586     "Reply with whether or not we're done yet."
587     (let ((am-i-done? (= (worker-task-left worker) 0)))
588       (if am-i-done?
589           (display "worker> Yes, I finished up!\n")
590           (display "worker> No... I'm still working on it...\n"))
591       (<-reply message am-i-done?)))
592
593   (define (worker-go-home worker message)
594     "It's off of work for us!"
595     (display "worker> Whew!  Free at last.\n")
596     (self-destruct worker))
597 #+END_SRC
598
599 (As you've probably guessed, you wouldn't normally call =display=
600 everywhere as we are in this program... that's just to make the
601 examples more illustrative.)
602
603 "<-reply" is what actually returns the information to the actor
604 waiting on the reply.
605 It takes as an argument the actor sending the message, the message
606 it is in reply to, and the rest of the arguments are the "body" of
607 the message.
608 (If an actor handles a message that is being "waited on" but does not
609 explicitly reply to it, an auto-reply with an empty body will be
610 triggered so that the waiting actor is not left waiting around.)
611
612 The last thing to note is the call to "self-destruct".
613 This does what you might expect: it removes the actor from the hive.
614 No new messages will be sent to it.
615 Ka-poof!
616
617 Running it is the same as before:
618
619 #+BEGIN_SRC scheme
620   (let* ((hive (make-hive))
621          (worker (bootstrap-actor hive <worker>))
622          (manager (bootstrap-actor hive <manager>
623                                    #:direct-report worker)))
624     (run-hive hive (list (bootstrap-message hive manager 'assign-task 5))))
625 #+END_SRC
626
627 But the output is a bit different:
628
629 #+BEGIN_SRC scheme
630 manager> Work on this task for me!
631 worker> Whatever you say, boss!
632 worker> *huff puff*
633 worker> *huff puff*
634 manager> Are you done yet???
635 worker> No... I'm still working on it...
636 manager> Harumph!
637 worker> *huff puff*
638 manager> Are you done yet???
639 worker> *huff puff*
640 worker> No... I'm still working on it...
641 manager> Harumph!
642 worker> *huff puff*
643 manager> Are you done yet???
644 worker> Yes, I finished up!
645 manager> Oh!  I guess you can go home then.
646 worker> Whew!  Free at last.
647 #+END_SRC
648
649 ** Writing our own network-enabled actor
650
651 So, you want to write a networked actor!
652 Well, luckily that's pretty easy, especially with all you know so far.
653
654 #+BEGIN_SRC scheme
655   (use-modules (oop goops)
656                (8sync)
657                (ice-9 rdelim)  ; line delineated i/o
658                (ice-9 match))  ; pattern matching
659
660   (define-actor <telcmd> (<actor>)
661     ((*init* telcmd-init)
662      (*cleanup* telcmd-cleanup)
663      (new-client telcmd-new-client))
664     (socket #:accessor telcmd-socket
665             #:init-value #f))
666 #+END_SRC
667
668 Nothing surprising about the actor definition, though we do see that
669 it has a slot for a socket.
670 Unsurprisingly, that will be set up in the =*init*= handler.
671
672 #+BEGIN_SRC scheme
673   (define (set-port-nonblocking! port)
674     (let ((flags (fcntl port F_GETFL)))
675       (fcntl port F_SETFL (logior O_NONBLOCK flags))))
676
677   (define (setup-socket)
678     ;; our socket
679     (define s
680       (socket PF_INET SOCK_STREAM 0))
681     ;; reuse port even if busy
682     (setsockopt s SOL_SOCKET SO_REUSEADDR 1)
683     ;; connect to port 8889 on localhost
684     (bind s AF_INET INADDR_LOOPBACK 8889)
685     ;; make it nonblocking and start listening
686     (set-port-nonblocking! s)
687     (listen s 5)
688     s)
689
690   (define (telcmd-init telcmd message)
691     (set! (telcmd-socket telcmd) (setup-socket))
692     (display "Connect like: telnet localhost 8889\n")
693     (while (actor-alive? telcmd)
694       (let ((client-connection (accept (telcmd-socket telcmd))))
695         (<- (actor-id telcmd) 'new-client client-connection))))
696
697   (define (telcmd-cleanup telcmd message)
698     (display "Closing socket!\n")
699     (when (telcmd-socket telcmd)
700       (close (telcmd-socket telcmd))))
701 #+END_SRC
702
703 That =setup-socket= code looks pretty hard to read!
704 But that's pretty standard code for setting up a socket.
705 One special thing is done though... the call to
706 =set-port-nonblocking!= sets flags on the socket port so that,
707 you guessed it, will be a nonblocking port.
708
709 This is put to immediate use in the telcmd-init method.
710 This code looks suspiciously like it /should/ block... after
711 all, it just keeps looping forever.
712 But since 8sync is using Guile's suspendable ports code feature,
713 so every time this loop hits the =accept= call, if that call
714 /would have/ blocked, instead this whole procedure suspends
715 to the scheduler... automatically!... allowing other code to run.
716
717 So, as soon as we do accept a connection, we send a message to
718 ourselves with the =new-client= action.
719 But wait!
720 Aren't actors only supposed to handle one message at a time?
721 If the telcmd-init loop just keeps on looping and looping,
722 when will the =new-client= message ever be handled?
723 8sync actors only receive one message at a time, but by default if an
724 actor's message handler suspends to the agenda for some reason (such
725 as to send a message or on handling I/O), that actor may continue to
726 accept other messages, but always in the same thread.[fn:queued-handler]
727
728 We also see that we've established a =*cleanup*= handler.
729 This is run any time either the actor dies, either through self
730 destructing, because the hive completes its work, or because
731 a signal was sent to interrupt or terminate our program.
732 In our case, we politely close the socket when =<telcmd>= dies.
733
734 #+BEGIN_SRC scheme
735   (define (telcmd-new-client telcmd message client-connection)
736     (define client (car client-connection))
737     (set-port-nonblocking! client)
738     (let loop ()
739       (let ((line (read-line client)))
740         (cond ((eof-object? line)
741                (close client))
742               (else
743                (telcmd-handle-line telcmd client
744                                    (string-trim-right line #\return))
745                (when (actor-alive? telcmd)
746                  (loop)))))))
747
748   (define (telcmd-handle-line telcmd client line)
749     (match (string-split line #\space)
750       (("") #f)  ; ignore empty lines
751       (("time" _ ...)
752        (display
753         (strftime "The time is: %c\n" (localtime (current-time)))
754         client))
755       (("echo" rest ...)
756        (format client "~a\n" (string-join rest " ")))
757       ;; default
758       (_ (display "Sorry, I don't know that command.\n" client))))
759 #+END_SRC
760
761 Okay, we have a client, so we handle it!
762 And once again... we see this goes off on a loop of its own!
763 (Also once again, we have to do the =set-port-nonblocking!= song and
764 dance.)
765 This loop also automatically suspends when it would otherwise block...
766 as long as read-line has information to process, it'll keep going, but
767 if it would have blocked waiting for input, then it would suspend the
768 agenda.[fn:setvbuf]
769
770 The actual method called whenever we have a "line" of input is pretty
771 straightforward... in fact it looks an awful lot like the IRC bot
772 handle-line procedure we used earlier.
773 No surprises there!
774
775 Now let's run it:
776
777 #+BEGIN_SRC scheme
778   (let* ((hive (make-hive))
779          (telcmd (bootstrap-actor hive <telcmd>)))
780     (run-hive hive '()))
781 #+END_SRC
782
783 Open up another terminal... you can connect via telnet:
784
785 #+BEGIN_SRC text
786 $ telnet localhost 8889
787 Trying 127.0.0.1...
788 Connected to localhost.
789 Escape character is '^]'.
790 time
791 The time is: Thu Jan  5 03:20:17 2017
792 echo this is an echo
793 this is an echo
794 shmmmmmmorp
795 Sorry, I don't know that command.
796 #+END_SRC
797
798 Horray, it works!
799 Type =Ctrl+] Ctrl+d= to exit telnet.
800
801 Not so bad!
802 There's more that could be optimized, but we'll consider that to be
803 advanced topics of discussion.
804
805 So that's a pretty solid intro to how 8sync works!
806 Now that you've gone through this introduction, we hope you'll have fun
807 writing and hooking together your own actors.
808 Since actors are so modular, it's easy to have a program that has
809 multiple subystems working together.
810 You could build a worker queue system that displayed a web interface
811 and spat out notifications about when tasks finish to IRC, and making
812 all those actors talk to each other should be a piece of cake.
813 The sky's the limit!
814
815 Happy hacking!
816
817 [fn:setvbuf]
818   If there's a lot of data coming in and you don't want your I/O loop
819   to become too "greedy", take a look at =setvbuf=.
820
821 [fn:queued-handler]
822   This is customizable: an actor can be set up to queue messages so
823   that absolutely no messages are handled until the actor completely
824   finishes handling one message.
825   Our loop couldn't look quite like this though!
826
827 ** An intermission: about live hacking
828
829 This section is optional, but highly recommended.
830 It requires that you're a user of GNU Emacs.
831 If you aren't, don't worry... you can forge ahead and come back in case
832 you ever do become an Emacs user.
833 (If you're more familiar with Vi/Vim style editing, I hear good things
834 about Spacemacs...)
835
836 Remember all the way back when we were working on the IRC bot?
837 So you may have noticed while updating that section that the
838 start/stop cycle of hacking isn't really ideal.
839 You might either edit a file in your editor, then run it, or
840 type the whole program into the REPL, but then you'll have to spend
841 extra time copying it to a file.
842 Wouldn't it be nice if it were possible to both write code in a
843 file and try it as you go?
844 And wouldn't it be even better if you could live edit a program
845 while it's running?
846
847 Luckily, there's a great Emacs mode called Geiser which makes
848 editing and hacking and experimenting all happen in harmony.
849 And even better, 8sync is optimized for this experience.
850 8sync provides easy drop-in "cooperative REPL" support, and
851 most code can be simply redefined on the fly in 8sync through Geiser
852 and actors will immediately update their behavior, so you can test
853 and tweak things as you go.
854
855 Okay, enough talking.  Let's add it!
856 Redefine run-bot like so:
857
858 #+BEGIN_SRC scheme
859   (define* (run-bot #:key (username "examplebot")
860                     (server "irc.freenode.net")
861                     (channels '("##botchat"))
862                     (repl-path "/tmp/8sync-repl"))
863     (define hive (make-hive))
864     (define irc-bot
865       (bootstrap-actor* hive <my-irc-bot> "irc-bot"
866                         #:username username
867                         #:server server
868                         #:channels channels))
869     (define repl-manager
870       (bootstrap-actor* hive <repl-manager> "repl"
871                           #:path repl-path))
872
873     (run-hive hive '()))
874 #+END_SRC
875
876 If we put a call to run-bot at the bottom of our file we can call it,
877 and the repl-manager will start something we can connect to automatically.
878 Horray!
879 Now when we run this it'll start up a REPL with a unix domain socket at
880 the repl-path.
881 We can connect to it in emacs like so:
882
883 : M-x geiser-connect-local <RET> guile <RET> /tmp/8sync-repl <RET>
884
885 Okay, so what does this get us?
886 Well, we can now live edit our program.
887 Let's change how our bot behaves a bit.
888 Let's change handle-line and tweak how the bot responds to a botsnack.
889 Change this part:
890
891 #+BEGIN_SRC scheme
892   ;; From this:
893   ("botsnack"
894    (respond "Yippie! *does a dance!*"))
895
896   ;; To this:
897   ("botsnack"
898    (respond "Yippie! *catches botsnack in midair!*"))
899 #+END_SRC
900
901 Okay, now let's evaluate the change of the definition.
902 You can hit "C-M-x" anywhere in the definition to re-evaluate.
903 (You can also position your cursor at the end of the definition and press
904 "C-x C-e", but I've come to like "C-M-x" better because I can evaluate as soon
905 as I'm done writing.)
906 Now, on IRC, ask your bot for a botsnack.
907 The bot should give the new message... with no need to stop and start the
908 program!
909
910 Let's fix a bug live.
911 Our current program works great if you talk to your bot in the same
912 IRC channel, but what if you try to talk to them over private message?
913
914 #+BEGIN_SRC text
915 IRC> /query examplebot
916 <foo-user> examplebot: hi!
917 #+END_SRC
918
919 Hm, we aren't seeing any response on IRC!
920 Huh?  What's going on?
921 It's time to do some debugging.
922 There are plenty of debugging tools in Guile, but sometimes the simplest
923 is the nicest, and the simplest debugging route around is good old
924 fashioned print debugging.
925
926 It turns out Guile has an under-advertised feature which makes print
927 debugging really easy called "pk", pronounced "peek".
928 What pk accepts a list of arguments, prints out the whole thing,
929 but returns the last argument.
930 This makes wrapping bits of our code pretty easy to see what's
931 going on.
932 So let's peek into our program with pk.
933 Edit the respond section to see what channel it's really sending
934 things to:
935
936 #+BEGIN_SRC scheme
937   (define-method (handle-line (irc-bot <my-irc-bot>) message
938                               speaker channel line emote?)
939     ;; [... snip ...]
940     (define (respond respond-line)
941       (<- (actor-id irc-bot) 'send-line (pk 'channel channel)
942           respond-line))
943     ;; [... snip ...]
944     )
945 #+END_SRC
946
947 Re-evaluate.
948 Now let's ping our bot in both the channel and over PM.
949
950 #+BEGIN_SRC text
951 ;;; (channel "##botchat")
952
953 ;;; (channel "sinkbot")
954 #+END_SRC
955
956 Oh okay, this makes sense.
957 When we're talking in a normal multi-user channel, the channel we see
958 the message coming from is the same one we send to.
959 But over PM, the channel is a username, and in this case the username
960 we're sending our line of text to is ourselves.
961 That isn't what we want.
962 Let's edit our code so that if we see that the channel we're sending
963 to looks like our own username that we respond back to the sender.
964 (We can remove the pk now that we know what's going on.)
965
966 #+BEGIN_SRC scheme
967   (define-method (handle-line (irc-bot <my-irc-bot>) message
968                               speaker channel line emote?)
969     ;; [... snip ...]
970     (define (respond respond-line)
971       (<- (actor-id irc-bot) 'send-line
972           (if (looks-like-me? channel)
973               speaker    ; PM session
974               channel)   ; normal IRC channel
975           respond-line))
976     ;; [... snip ...]
977     )
978 #+END_SRC
979
980 Re-evaluate and test.
981
982 #+BEGIN_SRC text
983 IRC> /query examplebot
984 <foo-user> examplebot: hi!
985 <examplebot> Oh hi foo-user!
986 #+END_SRC
987
988 Horray!
989
990
991 * API reference
992
993 * Systems reference
994 ** IRC
995 ** Web / HTTP
996 ** COMMENT Websockets
997
998 * Addendum
999 ** Recommended .emacs additions
1000
1001 In order for =mbody-receive= to indent properly, put this in your
1002 .emacs:
1003
1004 #+BEGIN_SRC emacs-lisp
1005 (put 'mbody-receive 'scheme-indent-function 2)
1006 #+END_SRC
1007
1008 ** 8sync and Fibers
1009
1010 One other major library for asynchronous communication in Guile-land
1011 is [[https://github.com/wingo/fibers/][Fibers]].
1012 There's a lot of overlap:
1013
1014  - Both use Guile's suspendable-ports facility
1015  - Both communicate between asynchronous processes using message passing;
1016    you don't have to squint hard to see the relationship between Fibers'
1017    channels and 8sync's actor inboxes.
1018
1019 However, there are clearly differences too.
1020 There's a one to one relationship between 8sync actors and an actor inbox,
1021 whereas each Fibers fiber may read from multiple channels, for example.
1022
1023 Luckily, it turns out there's a clear relationship, based on real,
1024 actual theory!
1025 8sync is based on the [[https://en.wikipedia.org/wiki/Actor_model][actor model]] whereas fibers follows
1026 [[http://usingcsp.com/][Communicating Sequential Processes (CSP)]], which is a form of
1027 [[https://en.wikipedia.org/wiki/Process_calculus][process calculi]]. 
1028 And it turns out, the
1029 [[https://en.wikipedia.org/wiki/Actor_model_and_process_calculi][relationship between the actor model and process calculi]] is well documented,
1030 and even more precisely, the
1031 [[https://en.wikipedia.org/wiki/Communicating_sequential_processes#Comparison_with_the_Actor_Model][relationship between CSP and the actor model]] is well understood too.
1032
1033 So, 8sync and Fibers do take somewhat different approaches, but both
1034 have a solid theoretical backing... and their theories are well
1035 understood in terms of each other.
1036 Good news for theory nerds!
1037
1038 (Since the actors and CSP are [[https://en.wikipedia.org/wiki/Dual_%28mathematics%29][dual]], maybe eventually 8sync will be
1039 implemented on top of Fibers... that remains to be seen!)
1040