Ch 10 - 12 Spree!
Jul 20, 2020
Contents
What I learned today
Concurrent-ify tasks that are completely independent of each other. No risk since there is no shared access to a mutable state
pmap
-> each application of the mapping function happens on a separate thread
;; (time (dorun (map clojure.string/lower-case orc-names)))
;; "Elapsed time: 295.426771 msecs"
;; (time (dorun (pmap clojure.string/lower-case orc-names)))
;; "Elapsed time: 129.370606 msecs"
There is some overhead involved with creating and coordinating threads. grain size - amount of work done by each parallelized task. Sometimes, the overhead can be more than the time for each function application.
Solution to make pmap
take less time is to increase the grain size. (applying the mapping function to two elements instead of one)
Chapter 10 Exercises
- Create an atom with the initial value 0, use swap! to increment it a couple of times, and then dereference it.
(def a (atom 0))
(swap! a inc)
(swap! a inc)
@a
;; => 2
- Create a function that uses futures to parallelize the task of downloading random quotes fromhttp://www.braveclojure.com/random-quote
(defn get-quote [] (slurp "https://www.braveclojure.com/random-quote"))
(defn quote-word-count
[num-quotes]
(let [word-freq (atom {})]
(dotimes [i num-quotes]
(deref (future (let [cur-quote (as-> (get-quote) x
(clojure.string/replace x #"--" "")
(clojure.string/replace x #"\n" "")
(clojure.string/lower-case x))
cur-freq (frequencies (clojure.string/split cur-quote #" "))]
(swap! word-freq merge cur-freq)))))
(deref word-freq)))
(println (quote-word-count 5))
- Create representations of two characters in a game. The first character has 15 hit points out of a total of 40. The second character has a healing potion in his inventory. Use refs and transactions to model the consumption of the healing potion and the first character healing.
(def player1 (ref {:handle "Kirito" :hitpoints 15/40 :inventory {:sword "Dual Blades" :healing-potion 0}}))
(def player2 (ref {:handle "Asuna" :hitpoints 33/40 :inventory {:healing-potion 1}}))
(dosync
(alter player2 update-in [:inventory :healing-potion] dec)
(alter player1 assoc :hitpoints 40/40))
@player1
;; => {:handle "Kirito",
;; :hitpoints 40/40,
;; :inventory {:sword "Dual Blades", :healing-potion 0}}
@player2
;; => {:handle "Asuna", :hitpoints 33/40, :inventory {:healing-potion 0}}
Chapter 11
core.async describes a useful model for concurrency and processes.
- process -> concurrently running unit of logic that responds to events
go
- creates a new process.go
blocks execute concurrently on a separate thread.<!
- take function. Listens to the channel and the process waits until a message arrives in the channel.- use chan buffers to put multiple values into a channel without waiting
- parking - frees up the thread so it can keep doing work. allows instructions from multiple processes to interleave on a single thread. only possible within
go
blocks.
Chapter 12 - JVM
Working with the JVM!
- Clojure is hosted on the JVM.
- Need to use Java objects for core functionality like reading files and dates
- Java has a lot of libraries
The JVM translates code into low-level instructions called Java Bytecode. JIT compilation - A running JVM executes bytecode by translating it on the fly into into machine code that its host will understand.
-
Compile a program into a .class file -> packaged into a JAR file -> JVM executes the bytecode -> JVM sends machine instructions to the CPU.
-
(:gen-class)
- Clojure compiler generates a class for the namespace -
Define a
-main
function in a namespace, include(:gen-class)
and set the:main
directive in project.clj ot let the program be compiled by Java as a JAR.
Interop
Call methods on an object using (.methodName object)
. This uses macros to expand the dot special form.
(macroexpand-1 '(.toUpperCase "By Bluebeard's bananas!"))
; => (. "By Bluebeard's bananas!" toUpperCase)
(macroexpand-1 '(.indexOf "Let's synergize our bleeding edges" "y"))
; => (. "Let's synergize our bleeding edges" indexOf "y")
(macroexpand-1 '(Math/abs -3))
; => (. Math abs -3)
Create objects with (new ClassName opt-args)
or (Classname. opt-args)
Importing has the same effect as it does in Java -> You can use classes without having to type out the entire package prefix.
(import java.util.Stack.)
Within a ns
macro -
(ns pirate.talk
(:import [java.util Date Stack]
[java.net Proxy URI]))
You can use seq functions to read data from a stack. But can’t use conj or into
(doto (java.util.Stack.)
(.push "Latest ep of GoT")
(.push "Whoops, I meant something else"))
;; doto returns the object rather than the value of the method calls
(System/getenv)
(System/getProperty "user.dir")
;; => "/Users/mani/repos/learn-clojure-in-public/code/clojure-noob"
(System/getProperty "java.version")
;; => "14.0.1"
IO with Clojure -
(spit "/tmp/hercules-todo-list"
"- kill dat lion brov
- chop up what nasty multi-headed snake thing")
(slurp "/tmp/hercules-todo-list")
;; => "- kill dat lion brov\n- chop up what nasty multi-headed snake thing"
(with-open [todo-list-str (clojure.java.io/reader "/tmp/hercules-todo-list")]
(println (first (line-seq todo-list-str))))
;; => kill dat lion bruv
Takeaway
I went through quite a few different pieces of information about Clojure today. But the downside is that I didn’t gain a deep understanding of it. So I’ll have to go back and understand it.
Today’s tally -
- Complete ch 10
- Complete ch 11
- Complete ch 12