Asynchronous processing

In this chapter, you will write an étude that uses core.async to do asynchronous processing. Even though the JavaScript environment is single-threaded, core.async allows you to work with anything that needs to be handled asynchronously; this is a very nice feature indeed.

Here are two examples of using core.async. In the first example, Annie and Brian are going to send each other the numbers 5 down to zero, stopping at zero, in a project named async1. You will need to add some :require and :require-macro specifications to your namespace:

(ns ^:figwheel-always async1.core
  (:require-macros [cljs.core.async.macros :refer [go go-loop]])
    (:require [cljs.core.async
               :refer [<! >! timeout alts! chan close!]]))

Then, define a channel for both Annie and Brian:

(def annie (chan))
(def brian (chan))

Annie gets two processes: one for sending messages to Brian and another for receiving messages from him.

(defn annie-send []
  (go (loop [n 5]
           (println "Annie sends" n "t` Brian")
           (>! brian n)
           (when (pos? n) (recur (dec n))))))

(defn annie-receive []
  (go-loop []
           (let [reply (<! brian)]
             (println "Annie receives" reply "from Brian")
             (if (pos? reply)
               (recur)
               (close! annie)))))

In the annie-send function, you see the go function, which asynchronously executes its body and immediately returns to the calling function. The >! function sends data to a channel. The loop continues until n equals zero, at which point the function returns nil.

Because go and loop occur together so often, ClojureScript has the go-loop construct, which you see in the annie-receive function. That function loops (but does not need the loop variable) until it has received the zero, at which point it performs a close! on the channel.

A similar pair of functions brian-send and brian-receive do Brian’s sending and receiving tasks (they are not shown here). You may have noticed there’s a lot of duplication here; we’ll get rid of it in the next example.

All that remains to be done is write a function that invokes these processes:

(defn async-test []
  (do
    (println "Starting...")
    (annie-send)
    (annie-receive)
    (brian-send)
    (brian-receive)))

Here is the console log output from invoking async-test. You can see that this is indeed asynchronous; the sends and receives are in no particular order.

Starting...
Annie: 5 -> Brian 
Annie: 5 <- Brian 
Brian: 5 -> Annie 
Brian: 5 <- Annie 
Annie: 4 -> Brian 
Annie: 3 -> Brian 
Brian: 4 -> Annie 
Brian: 3 -> Annie 
Annie: 4 <- Brian 
Annie: 3 <- Brian 
Brian: 4 <- Annie 
Brian: 3 <- Annie 
Annie: 2 -> Brian 
Annie: 1 -> Brian 
Brian: 2 -> Annie 
Brian: 1 -> Annie 
Annie: 2 <- Brian 
Annie: 1 <- Brian 
Brian: 2 <- Annie 
Brian: 1 <- Annie 
Annie: 0 -> Brian 
Brian: 0 -> Annie 
Annie: 0 <- Brian 
Brian: 0 <- Annie 

You can see the entire program here: Sample core.async Program 1.

The next example using core.async, in a project named async2, has processes that communicate with one another in a semi-synchronized manner. In this case, Annie will start off by sending Brian the number 8; he will send her a 7; she sends back 6, and so on, down to zero.

In this case, both people do the same thing: send the next lower number to their partner, then await the partner’s reply. Here is the function to activate the process for the two partners. The from-str and to-str parameters are used for the debug output.

(defn decrement! [[from-str from-chan] [to-str to-chan] & [start-value]]
  (go-loop [n (or start-value (dec (<! from-chan)))]
           (println from-str ":" n "->" to-str)
           (>! to-chan n)
           (when-let [reply (<! from-chan)]
             (println from-str ":" reply "<-" to-str)
             (if (pos? reply)
               (recur (dec reply))
               (do
                 (close! from-chan)
                 (close! to-chan)
                 (println "Finished"))))))

There are several clever tricks going on in this function. The & [start-value] allows an optional starting value. There’s an asymmetry in the processes; Annie starts the sending, and Brian starts by receiving her data. Thus, Annie will start with 8 as her start-value; Brian will omit that argument. The completion of this bit of kabuki is in (or start-value (dec (<! from-chan))); if start-value is nil (which evaluates to false), you take one less than the received value as your starting value.

Similarly, the when-let clause is executed only when the reply from from-chan is true (i.e., not nil).

(defn async-test []
  (let [annie (chan)
        brian (chan)]
    (activate ["Annie" annie] ["Brian" brian] 8)
    (activate ["Brian" brian] ["Annie" annie])))

Here is the output from invoking async-test.

Annie : 8 -> Brian
Brian : 7 -> Annie
Annie : 7 <- Brian
Annie : 6 -> Brian
Brian : 6 <- Annie
Brian : 5 -> Annie
Annie : 5 <- Brian
Annie : 4 -> Brian
Brian : 4 <- Annie
Brian : 3 -> Annie
Annie : 3 <- Brian
Annie : 2 -> Brian
Brian : 2 <- Annie
Brian : 1 -> Annie
Annie : 1 <- Brian
Annie : 0 -> Brian
Brian : 0 <- Annie
Finished

You can see the entire program here: Sample core.async Program 2.

Étude 8-1: TBD

In this étude, you’re going to write a program that lets the computer play the card game of "War" against itself.

The Art of War

(Apologies to Sun Tzu.) These are the rules of the game as condensed from Wikipedia, adapted to two players, and simplified further.

Two players each take 26 cards from a shuffled deck. Each person puts their top card face up on the table. Whoever has the higher value card wins that battle, takes both cards, and puts them at the bottom of her stack. What happens the if the cards have the same value? Then the players go to "war." Each person puts the next two cards from their stack face down in the pile and a third card face up. High card wins, and the winner takes all the cards for the bottom of their stack. If the cards match again, the war continues with another set of three cards from each person. If a person has fewer than three cards when a war happens, they put in all their cards.

Repeat this entire procedure until one person has all the cards. That player wins the game. In this game, aces are considered to have the highest value, and King > Queen > Jack.

A game can go on for a very long time, so I have added a new rule: if the game goes more than a pre-determined maximum number of rounds (50 in my program), stop playing. The person who has fewer cards win. If the number of cards is equal, it’s a tie.

War: What is it good for?

Absolutely nothing. Well, almost nothing. War is possibly the most incredibly inane card game ever invented. It is a great way for children to spend time, and it’s perfect as an étude because

  • it is naturally implementable as channels (players) passing information (cards)
  • there is no strategy involved in the play, thus allowing you to concentrate on the channels and messages

Pay Now or Pay Later

When you purchase an item, if you pay cash on the spot, you often end up paying less than if you used credit. If you are cooking a meal, getting all of the ingredients collected before you start (pay now) is often less stressful than having to stop and go to the grocery store for items you found out you didn’t have (pay later). In most cases, “pay now” ends up being less expensive than “pay later,” and that certainly applies to most programming tasks.

So, before you rush off to start writing code, let me give you a word of advice: Don’t. Spend some time with paper and pencil, away from the computer, and design this program first. This is a non-trivial program, and the “extra” time you spend planning it (pay now) will save you a lot of time in debugging and rewriting (pay later). As someone once told me, “Hours of programming will save you minutes of planning.”

Trust me, programs written at the keyboard look like it, and that is not meant as a compliment.

Note: This does not mean that you should never use the REPL or write anything at the keyboard. If you are wondering about how a specific part of ClojureScript works and need to write a small test program to find out, go ahead and do that right away.

Hint: Do your design on paper. Don’t try to keep the whole thing in your head. Draw diagrams. Sometimes a picture or a storyboard of how the messages should flow will clarify your thinking. (If your parents ever asked you, “Do I have to draw you a diagram?”, you may now confidently answer “Yes. Please do that. It really helps.”)

The Design

When I first started planning this, I was going to have just two processes communicating with one another, as it is in a real game. But let’s think about that. There is a slight asymmetry between the players. One person usually brings the cards and suggests playing the game. He shuffles the deck and deals out the cards at the beginning. Once that’s done, things even out. The game play itself proceeds almost automatically. Neither player is in control of the play, yet both of them are. It seems as if there is an implicit, almost telepathic communication between the players. Of course, there are no profound metaphysical issues here. Both players are simultaneously following the same set of rules. And that’s the point that bothered me: who makes the “decisions” in the program? I decided to sidestep the issue by introducing a third agent, the dealer, who is responsible for giving the cards to each player at the start of the game. The dealer then can tell each player to turn over cards, make a decision as to who won, and then tell a particular player to take cards. This simplifies the message flow considerably.

In my code, the dealer had to keep track of:

  • The players’ channels
  • The current state of play (see the following)
  • Cards received from player 1 for this battle
  • Cards received from player 2 for this battle
  • The pile of cards in the middle of the table

The dealer initializes the players, and then is in one of the following states. I’m going to anthropomorphize and use “me” to represent the dealer.

Pre-battle
Tell the players to send me cards. If the pile is empty, then it’s a normal battle; give me one card each. If the pile isn’t empty, then it’s a war; give me three cards.
Battle
Wait to receive the cards from the players. If either player has sent me an empty list for their cards, then that player is out of cards, so the other player must be the winner. Send both players a message to quit looping for messages.

If I really have cards from both players, compare them. If one player has the high card, give that player the pile plus the cards currently in play, and go into post-battle state. Otherwise, the cards match. Add the cards currently in play to the pile, and go back to “Pre-battle” state.

Post-battle
Wait for the person to pick up the cards sent by the dealer. If you’ve hit the maximum number of rounds, go to long-game state. Otherwise, go back to “Pre-battle” state.
Long-game
The game has taken too many moves. Ask both players for the number of cards they have, and tell them both to quit looping. The winner is the player with the smaller number of cards (or a tie if they have the same number of cards).

Note that this is my implementation; you may find an entirely different and better way to write the program.

Messages Are Asynchronous

Remember that the order in which a process receives messages may not be the same order in which they were sent. For example, if players Annie and Brian have a battle, and Annie wins, you may be tempted to send these messages:

  1. Tell Annie to pick up the two cards that were in the battle.
  2. Tell Annie to send you a card for the next battle.
  3. Tell Brian to send you a card for the next battle.

This works nicely unless Annie had just thrown her last card down for that battle and message two arrives before message one. Annie will report that she is out of cards, thus losing the game, even though she’s really still in the game with the two cards that she hasn’t picked up yet.

Representing the Deck

I decided to represent the deck as a vector of the numbers 0 through 51 (inclusive); 0 through 12 are the Ace through King of clubs, 13 through 25 are diamonds, then hearts, then spades. (That is, the suits are in English alphabetical order.) You will find ClojureScript’s shuffle function to be quite useful. I wrote a small module in a file named utils.cljs for functions such as converting a card number to its suit and name and finding a card’s value.

If you want to make a web-based version of the game, you will find a set of SVG images of playing cards in the datafiles/chapter08/images directory, with names 0.svg through 51.svg. These file names correspond to the numbering described in the preceding paragraph. The file blue_grid_back.svg contains the image of the back of a playing card.

Note: You may want to generate a small deck with, say, only four cards in two suits. If you try to play with a full deck, the game could go on for a very long time.

Here is output from a game:

Starting Player 1 with [2 0 16 13 14 18]
Starting Player 2 with [1 4 3 15 17 5]
** Starting round 1
Player 1 has [2 0 16 13 14 18] sending dealer (2)
Player 2 has [1 4 3 15 17 5] sending dealer (1)
3 of clubs vs. 2 of clubs
Player 2 receives [2 1] add to [4 3 15 17 5]
** Starting round 2
Player 1 has [0 16 13 14 18] sending dealer (0)
Player 2 has [4 3 15 17 5 2 1] sending dealer (4)
Ace of clubs vs. 5 of clubs
Player 2 receives [0 4] add to [3 15 17 5 2 1]
** Starting round 3
Player 1 has [16 13 14 18] sending dealer (16)
Player 2 has [3 15 17 5 2 1 0 4] sending dealer (3)
4 of diamonds vs. 4 of clubs
** Starting round 4
Player 2 has [15 17 5 2 1 0 4] sending dealer (15 17 5)
Player 1 has [13 14 18] sending dealer (13 14 18)
6 of diamonds vs. 6 of clubs
** Starting round 5
Player 1 has [] sending dealer ()
Player 2 has [2 1 0 4] sending dealer (2 1 0)
nil vs. Ace of clubs
Winner: Player 1

See a suggested solution: Solution 8-1.