Created
December 23, 2015 02:07
-
-
Save kunday/7581bad771c3eef11df2 to your computer and use it in GitHub Desktop.
Async Clojure Gorilla Doc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ;; gorilla-repl.fileformat = 1 | |
| ;; ** | |
| ;;; # core.async | |
| ;;; | |
| ;;; Welcome to core.async study session. | |
| ;;; | |
| ;;; Let's get started by setting up all the necessary imports. | |
| ;;; | |
| ;; ** | |
| ;; @@ | |
| (ns clj-study-group.core | |
| (:require [clojure.core.async | |
| :as a | |
| :refer [>! <! >!! <!! go chan buffer close! thread | |
| alts! alts!! timeout sliding-buffer dropping-buffer]])) | |
| ;; @@ | |
| ;; => | |
| ;;; {"type":"html","content":"<span class='clj-nil'>nil</span>","value":"nil"} | |
| ;; <= | |
| ;; ** | |
| ;;; ## Part 1. Basics | |
| ;;; | |
| ;;; Now let's create a new channel. | |
| ;;; | |
| ;;; `chan` is the function will get you there. | |
| ;; ** | |
| ;; @@ | |
| (def echo-chan (chan)) | |
| ;; @@ | |
| ;; => | |
| ;;; {"type":"html","content":"<span class='clj-var'>#'clj-study-group.core/echo-chan</span>","value":"#'clj-study-group.core/echo-chan"} | |
| ;; <= | |
| ;; ** | |
| ;;; Channel - Creates a new channel that can receive messages. | |
| ;;; You can put or take messages off of a channel. | |
| ;; ** | |
| ;; ** | |
| ;;; Now lets' create a new process. | |
| ;;; | |
| ;;; Processes have to obey these rules | |
| ;;; * Wait until put or take suceeds | |
| ;;; * When the prev one succeeds continue executing | |
| ;;; | |
| ;;; | |
| ;;; `go` - Will create a new process on a seperate thread | |
| ;;; number of threads available = 2 + number of cores in machine. | |
| ;;; | |
| ;;; | |
| ;;; Two concepts: | |
| ;;; 1. Put & | |
| ;;; 2. Take | |
| ;;; | |
| ;; ** | |
| ;; ** | |
| ;;; Lets' take message of a channel | |
| ;;; `<!` is the take function | |
| ;; ** | |
| ;; @@ | |
| (go (println (<! echo-chan))) | |
| ;; @@ | |
| ;; => | |
| ;;; {"type":"html","content":"<span class='clj-unkown'>#object[clojure.core.async.impl.channels.ManyToManyChannel 0x558507c0 "clojure.core.async.impl.channels.ManyToManyChannel@558507c0"]</span>","value":"#object[clojure.core.async.impl.channels.ManyToManyChannel 0x558507c0 \"clojure.core.async.impl.channels.ManyToManyChannel@558507c0\"]"} | |
| ;; <= | |
| ;; ** | |
| ;;; This function takes a message of the previous channel and executes println on the message. | |
| ;;; | |
| ;;; | |
| ;;; Let's put something in the channel | |
| ;;; `>!!` is the put function | |
| ;; ** | |
| ;; @@ | |
| (>!! echo-chan "test ketchup") | |
| ;; @@ | |
| ;; => | |
| ;;; {"type":"html","content":"<span class='clj-unkown'>true</span>","value":"true"} | |
| ;; <= | |
| ;; ** | |
| ;;; the above function puts a string 'ketchup' to the channel we created. | |
| ;;; | |
| ;;; Remember putting a message also waits for the message to be consumed. | |
| ;;; | |
| ;;; | |
| ;;; Will return `true` when successful. | |
| ;; ** | |
| ;; ** | |
| ;;; ## Part 2: Buffering | |
| ;;; | |
| ;;; Now let's create a function with a buffer size 2 | |
| ;; ** | |
| ;; @@ | |
| (def echo-buffer (chan 2)) | |
| (>!! echo-buffer "ketchup1") | |
| (>!! echo-buffer "ketchup2") | |
| ;; @@ | |
| ;; => | |
| ;;; {"type":"html","content":"<span class='clj-unkown'>true</span>","value":"true"} | |
| ;; <= | |
| ;; ** | |
| ;;; Running this line will block this repl. | |
| ;;; | |
| ;;; | |
| ;;; ```clojure | |
| ;;; (>!! echo-buffer "ketchup2") | |
| ;;; ``` | |
| ;;; Doing it another time will block untill somebody consumes the message. | |
| ;;; | |
| ;;; Let's consume a message from the buffer | |
| ;; ** | |
| ;; @@ | |
| (go (println (<! echo-buffer))) | |
| (clojure.pprint/pprint "this won't block the channel") | |
| (>!! echo-buffer "ketchup2") | |
| ;; @@ | |
| ;; -> | |
| ;;; "this ketchup1wo | |
| ;;; n't block the channel" | |
| ;;; | |
| ;; <- | |
| ;; => | |
| ;;; {"type":"html","content":"<span class='clj-unkown'>true</span>","value":"true"} | |
| ;; <= | |
| ;; ** | |
| ;;; | |
| ;;; Two types of buffers are available for this situation. | |
| ;;; | |
| ;;; 1. `sliding-buffer` drops values FIFO | |
| ;;; 2. `dropping-buffer` drops values LIFO | |
| ;; ** | |
| ;; ** | |
| ;;; ### Sliding Buffer | |
| ;; ** | |
| ;; @@ | |
| (def sliding-channel-buffer (chan (sliding-buffer 2))) | |
| ; Let's send some meessages to the sliding-buff | |
| (>!! sliding-channel-buffer "buffer_val_1") | |
| (>!! sliding-channel-buffer "buffer_val_2") | |
| (>!! sliding-channel-buffer "buffer_val_3") | |
| (>!! sliding-channel-buffer "buffer_val_4") | |
| (>!! sliding-channel-buffer "buffer_val_5") | |
| (go (println (<! sliding-channel-buffer))) | |
| (go (println (<! sliding-channel-buffer))) | |
| ;; @@ | |
| ;; -> | |
| ;;; buffer_val_4 | |
| ;;; | |
| ;; <- | |
| ;; => | |
| ;;; {"type":"html","content":"<span class='clj-unkown'>#object[clojure.core.async.impl.channels.ManyToManyChannel 0x11422c5f "clojure.core.async.impl.channels.ManyToManyChannel@11422c5f"]</span>","value":"#object[clojure.core.async.impl.channels.ManyToManyChannel 0x11422c5f \"clojure.core.async.impl.channels.ManyToManyChannel@11422c5f\"]"} | |
| ;; <= | |
| ;; ** | |
| ;;; | |
| ;;; 2This did not block because the buffer retains two messages and drops the first 2. | |
| ;; ** | |
| ;; ** | |
| ;;; ### Dropping Buffer | |
| ;; ** | |
| ;; @@ | |
| (clojure.pprint/pprint "Dropping buffer") | |
| ; 2b Create a dropping buffer | |
| (def dropping-channel-buffer (chan (dropping-buffer 2))) | |
| ; let's send some messages to the buffer. | |
| (>!! dropping-channel-buffer "buffer_val_5") | |
| (>!! dropping-channel-buffer "buffer_val_6") | |
| (>!! dropping-channel-buffer "buffer_val_7") | |
| (>!! dropping-channel-buffer "buffer_val_8") | |
| ; Our sliding buffer size is 2, and drops values FIFO | |
| (def th1 (go (println (<! dropping-channel-buffer)))) | |
| (def th2 (go (println (<! dropping-channel-buffer)))) | |
| (dorun (map <!! [th1 th2])) | |
| ;; @@ | |
| ;; -> | |
| ;;; "Dropping buffer" | |
| ;;; buffer_val_5 | |
| ;;; buffer_val_6 | |
| ;;; | |
| ;; <- | |
| ;; => | |
| ;;; {"type":"html","content":"<span class='clj-nil'>nil</span>","value":"nil"} | |
| ;; <= | |
| ;; ** | |
| ;;; ### Blocking vs Parking | |
| ;; ** | |
| ;; @@ | |
| (def hi-chan (chan)) | |
| (doseq [n (range 1000)] | |
| (go (>! hi-chan (str "hi " n)))) | |
| ;; @@ | |
| ;; => | |
| ;;; {"type":"html","content":"<span class='clj-nil'>nil</span>","value":"nil"} | |
| ;; <= | |
| ;; ** | |
| ;;; #### Blocking vs Parking | |
| ;;; | |
| ;;; 1. Parking ```<!``` & ```>!``` | |
| ;;; Parking frees up the thread so it can keep doing work - `future` | |
| ;;; | |
| ;;; 2. Blocking ```<!!``` & ```>!!``` | |
| ;;; Blocking blocks the thread and has to terminate - `thread` | |
| ;; ** | |
| ;; ** | |
| ;;; use `thread` when you need blocking options | |
| ;; ** | |
| ;; @@ | |
| (thread (println (<!! echo-chan))) | |
| (>!! echo-chan "mustard") | |
| ; => true | |
| ; => mustard | |
| ;; @@ | |
| ;; => | |
| ;;; {"type":"html","content":"<span class='clj-unkown'>true</span>","value":"true"} | |
| ;; <= | |
| ;; @@ | |
| (let [t (thread "chili")] | |
| (<!! t)) | |
| ;; @@ | |
| ;; => | |
| ;;; {"type":"html","content":"<span class='clj-string'>"chili"</span>","value":"\"chili\""} | |
| ;; <= | |
| ;; ** | |
| ;;; ## alts! | |
| ;;; | |
| ;;; | |
| ;;; lets you use the result of the first successful channel operation among a collection of operations. | |
| ;; ** | |
| ;; @@ | |
| (defn upload | |
| [headshot c] | |
| (go (Thread/sleep (rand 100)) | |
| (>! c headshot))) | |
| (let [c1 (chan) | |
| c2 (chan) | |
| c3 (chan)] | |
| (upload "serious.jpg" c1) | |
| (upload "fun.jpg" c2) | |
| (upload "sassy.jpg" c3) | |
| (let [[headshot channel] (alts!! [c1 c2 c3])] | |
| (println "Sending headshot notification for" headshot))) | |
| ;; @@ | |
| ;; -> | |
| ;;; Sending headshot notification for fun.jpg | |
| ;;; | |
| ;; <- | |
| ;; => | |
| ;;; {"type":"html","content":"<span class='clj-nil'>nil</span>","value":"nil"} | |
| ;; <= | |
| ;; ** | |
| ;;; In this case we just created a upload function that sleeps aribtrarily and echoes when done.. | |
| ;;; | |
| ;;; alt's takes a vector as an argument and when it does, waits for the first successful operation | |
| ;; ** | |
| ;; ** | |
| ;;; #### alts with timeout. | |
| ;;; | |
| ;;; Can be used to timeout a particular channel | |
| ;;; | |
| ;; ** | |
| ;; @@ | |
| (let [c1 (chan)] | |
| (upload "serious.jpg" c1) | |
| (let [[headshot channel] (alts!! [c1 (timeout 20)])] | |
| (if headshot | |
| (println "Sending headshot notification for" headshot) | |
| (println "Timed out!")))) | |
| ; => Timed out! | |
| ;; @@ | |
| ;; -> | |
| ;;; Timed out! | |
| ;;; | |
| ;; <- | |
| ;; => | |
| ;;; {"type":"html","content":"<span class='clj-nil'>nil</span>","value":"nil"} | |
| ;; <= | |
| ;; ** | |
| ;;; Like <!! and >!!, alts!! has a parking alternative, alts!, which you can use inside go blocks. | |
| ;; ** | |
| ;; ** | |
| ;;; ## Process Pipelines | |
| ;;; | |
| ;;; | |
| ;; ** | |
| ;; ** | |
| ;;; Similar to javascript callbacks. But through defined communication contracts | |
| ;;; | |
| ;; ** | |
| ;; @@ | |
| (defn upper-caser | |
| [in] | |
| (let [out (chan)] | |
| (go (while true (>! out (clojure.string/upper-case (<! in))))) | |
| out)) | |
| (defn reverser | |
| [in] | |
| (let [out (chan)] | |
| (go (while true (>! out (clojure.string/reverse (<! in))))) | |
| out)) | |
| (defn printer | |
| [in] | |
| (go (while true (println (<! in))))) | |
| ;; @@ | |
| ;; => | |
| ;;; {"type":"html","content":"<span class='clj-var'>#'clj-study-group.core/printer</span>","value":"#'clj-study-group.core/printer"} | |
| ;; <= | |
| ;; @@ | |
| (def in-chan (chan)) | |
| (def upper-caser-out (upper-caser in-chan)) | |
| (def reverser-out (reverser upper-caser-out)) | |
| (printer reverser-out) | |
| ; Let's try some blocking puts | |
| (>!! in-chan "redrum") | |
| (>!! in-chan "repaid") | |
| ;; @@ | |
| ;; -> | |
| ;;; MURDER | |
| ;;; DIAPER | |
| ;;; | |
| ;; <- | |
| ;; => | |
| ;;; {"type":"html","content":"<span class='clj-unkown'>true</span>","value":"true"} | |
| ;; <= | |
| ;; ** | |
| ;;; # Time to try it yourself | |
| ;;; | |
| ;; ** | |
| ;; ** | |
| ;;; Let's build a Rock Paper Scissors pipeline that does this. | |
| ;;; | |
| ;; ** | |
| ;; ** | |
| ;;; Things that might be useful for this problem: | |
| ;;; | |
| ;;; | |
| ;;; 1. 3 moves {rock, paper, scissor} | |
| ;;; 2. {rock beats scissors}, {paper beats rock}, {scissors beat paper} | |
| ;;; | |
| ;;; | |
| ;;; | |
| ;;; | |
| ;;; General structure: | |
| ;;; | |
| ;;; | |
| ;;; | |
| ;; ** | |
| ;; @@ | |
| (def MOVES [:rock :paper :scissors]) | |
| (def BEATS {:rock :scissors, :paper :rock, :scissors :paper}) | |
| (defn rand-player | |
| "Create a named player and return a channel to report moves." | |
| [name]) | |
| (defn winner | |
| "Based on two moves, return the name of the winner." | |
| [[name1 move1] [name2 move2]]) | |
| (defn judge | |
| "Given two channels on which players report moves, create and return an | |
| output channel to report the results of each match as [move1 move2 winner]." | |
| [p1 p2]) | |
| (defn init | |
| "Create 2 players (by default Alice and Bob) and return an output channel of match results." | |
| ([] (init "Alice" "Bob"))) | |
| (defn report | |
| "Report results of a match to the console." | |
| [[name1 move1] [name2 move2] winner] | |
| (println) | |
| (println name1 "throws" move1) | |
| (println name2 "throws" move2) | |
| (println winner "wins!")) | |
| (defn play | |
| "Play by taking a match reporting channel and reporting the results of the latest match." | |
| [out-chan] | |
| (apply report (<!! out-chan))) | |
| ;; @@ | |
| ;; => | |
| ;;; {"type":"html","content":"<span class='clj-var'>#'clj-study-group.core/play</span>","value":"#'clj-study-group.core/play"} | |
| ;; <= | |
| ;; ** | |
| ;;; | |
| ;;; ## Answer | |
| ;; ** | |
| ;; @@ | |
| (def MOVES [:rock :paper :scissors]) | |
| (def BEATS {:rock :scissors, :paper :rock, :scissors :paper}) | |
| (defn rand-player | |
| "Create a named player and return a channel to report moves." | |
| [name] | |
| (let [out (chan)] | |
| (go (while true (>! out [name (rand-nth MOVES)]))) | |
| out)) | |
| (defn winner | |
| "Based on two moves, return the name of the winner." | |
| [[name1 move1] [name2 move2]] | |
| (cond | |
| (= move1 move2) "no one" | |
| (= move2 (BEATS move1)) name1 | |
| :else name2)) | |
| (defn judge | |
| "Given two channels on which players report moves, create and return an | |
| output channel to report the results of each match as [move1 move2 winner]." | |
| [p1 p2] | |
| (let [out (chan)] | |
| (go | |
| (while true | |
| (let [m1 (<! p1) | |
| m2 (<! p2)] | |
| (>! out [m1 m2 (winner m1 m2)])))) | |
| out)) | |
| (defn init | |
| "Create 2 players (by default Alice and Bob) and return an output channel of match results." | |
| ([] (init "Alice" "Bob")) | |
| ([n1 n2] (judge (rand-player n1) (rand-player n2)))) | |
| (defn report | |
| "Report results of a match to the console." | |
| [[name1 move1] [name2 move2] winner] | |
| (println) | |
| (println name1 "throws" move1) | |
| (println name2 "throws" move2) | |
| (println winner "wins!")) | |
| (defn play | |
| "Play by taking a match reporting channel and reporting the results of the latest match." | |
| [out-chan] | |
| (apply report (<!! out-chan))) | |
| (def game (init)) | |
| (play game) | |
| ;; @@ | |
| ;; -> | |
| ;;; | |
| ;;; Alice throws :scissors | |
| ;;; Bob throws :scissors | |
| ;;; no one wins! | |
| ;;; | |
| ;; <- | |
| ;; => | |
| ;;; {"type":"html","content":"<span class='clj-nil'>nil</span>","value":"nil"} | |
| ;; <= | |
| ;; @@ | |
| ;; @@ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Nice worksheet!
But I have trouble seeing the output of those
printlns ingo-blocks while working in the gorilla repl. Is there a trick?Greetings from Germany