Skip to content

Instantly share code, notes, and snippets.

@kunday
Created December 23, 2015 02:07
Show Gist options
  • Select an option

  • Save kunday/7581bad771c3eef11df2 to your computer and use it in GitHub Desktop.

Select an option

Save kunday/7581bad771c3eef11df2 to your computer and use it in GitHub Desktop.
Async Clojure Gorilla Doc
;; 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'>#&#x27;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 &quot;clojure.core.async.impl.channels.ManyToManyChannel@558507c0&quot;]</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")
;; @@
;; ->
;;; &quot;this ketchup1wo
;;; n&#x27;t block the channel&quot;
;;;
;; <-
;; =>
;;; {"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 &quot;clojure.core.async.impl.channels.ManyToManyChannel@11422c5f&quot;]</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]))
;; @@
;; ->
;;; &quot;Dropping buffer&quot;
;;; 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'>&quot;chili&quot;</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'>#&#x27;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'>#&#x27;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"}
;; <=
;; @@
;; @@
@alphaone

Copy link
Copy Markdown

Nice worksheet!

But I have trouble seeing the output of those printlns in go-blocks while working in the gorilla repl. Is there a trick?

Greetings from Germany

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment