Today's topics:
- Part 1 - Boring basics
- Part 2 - Understanding the Parenthesis
- Part 3 - Playing with structures
- Part 4 - Functional transformation to sequences
- Part 5 - Software Transactional Memory
- Part 5.1 - Core.async
- Extra Part 6 - Data as code
- Extra Part 7 - Datomic
- Extra Part 8 - Transducers
- Extra Part 9 - Agents
- Extra Part 10 - ClojureScript
- Extra Part 11 - Clojure Spec
I'll assume you have docker. Let's try to run:
docker run --rm -e BOOT_LOCAL_REPO=/usr/src/app/.m2 -it -w /usr/src/app -v ${PWD}:/usr/src/app clojure:boot-alpine boot repl
If it is taking to much time, open your browser at: https://repl.it/languages/clojure and then in part three we will get back to your shell.
Now let's try to do this together. Don't be afraid to ask me to slow down or repeat something that I didn't explain well. Also feel free to just listen, and then only try it out at the end of each section.
How all books on a programming language start:
1
"hello"
'asd
:pto
[1 2 3 4 5 6 7 8 9 10]
{"a" 'b :c "d"}
'(a b c d)
println
Now you try
; (type X) -> Let's see the type of each primitive
(type 1)
(type "hello")
(type 'asd)
(type :pto)
(type [1 2 3 4 5 6 7 9 10])
(type {"a" 'b :c "d"})
(type '(a b c d))
(type println)
(type type)
Doing the Hello world
(println "hello world")
(+ 1 1)
(+ 2 (* 5 2))
But what is happening underneath?
Let's start from outside the parameters
'(println "Hello World")
(type '(println "Hello World"))
(eval '(println "Hello World"))
(println "hello world")
Now let's see inside the parameters.
What is the first parameter of the list?
(type println)
(type (fn [] "some function doing something"))
First hard concept, what is a symbol
(type 'println)
(type println)
(type (first '(println "hello world")))
Trying to put it all together
; (a (b (c d) e)) -> S expressions
(rand-nth [1 2 3 4])
( (rand-nth [+ - * rem]) ; -> first element of list
50 ; -> second element of list
100) ; -> third
How to write your own functions (starting very verbose and then moving to what we really use)
; We already wrote a function
(fn [] (println "hello world"))
(type (fn [] (println "hello world")))
; and we know how to call it
((fn [] (println "hello world")))
(type (fn [name] (println "hello world" name)))
((fn [name] (println "hello world" name)) "joao")
(def my-hello (fn [name] (println "hello world" name)))
(type my-hello)
(my-hello "joao")
; bonus question
(type 'my-hello)
(defn my-hello2 [name] (println "hello world" name))
(my-hello2 "joao")
You can use def for types different than functions
(def my-age 45)
my-age
Brainteaser - Symbols and how the repl works
(def a 1)
(def b a)
(def c b)
c ; ??? <-- do you know the answer?
; what about?
(def a 1)
(def b 'a)
(def c 'b)
(eval 'c)
(eval (eval 'c))
(eval (eval (eval 'c)))
Let's bring a http library. You now need to run commands in your own repl running in the docker container:
; Build system magic, not important right now
(set-env! :dependencies '[[clj-http "3.6.1"]])
(require '(clj-http [client]))
Who is a fan of start wars??
(def response (clj-http.client/get "https://swapi.co/api/planets/1/"))
(pprint response)
Let's understand what is the response
(type response)
(keys response)
(type (first (keys response)))
Keywords can be used as functions on maps
(keys response)
(:status response)
(:protocol-version response)
(:headers response)
(:body response)
(pprint (:body response))
(class (:body response))
Transform json to a clojure map
(set-env! :dependencies '[[clj-http "3.6.1"] [cheshire "5.7.1"]])
(require '(cheshire [core]))
(def body (cheshire.core/parse-string (:body response)))
body
(class body)
(keys body)
(pprint body)
(get body :name)
Tell cheshire to symbolize keys
(def body (cheshire.core/parse-string (:body response) true))
(pprint body)
(class body)
(keys body)
(:residents body)
(class (:residents body)) ;; It is a vector
Vector operations
(count (:residents body))
(first (:residents body))
(last (:residents body))
(nth (:residents body) 3) ;; Fetches the third element on the vector
Make it easier to refer to planets. https://swapi.co/api/planets/
(def response (clj-http.client/get "https://swapi.co/api/planets/"))
(def body (cheshire.core/parse-string (:body response) true))
(pprint body)
(keys body)
(def planets (:results body))
(pprint planets)
; Uppercase planet names
(map #(clojure.string/upper-case (:name %)) planets)
; Is equal to
(map (fn [p] (clojure.string/upper-case (:name p))) planets)
; I only want temperate planets
(def temperate-planets (filter #(= (:climate %) "temperate") planets))
(map #(:name %) temperate-planets)
; reduce/foldl/inject
(reduce + 0 [1 2 3 4])
; How to count the residents
(reduce
(fn [accum el]
(+ accum (count (:residents el))))
0
planets)
; Using let to temporarely assign values to symbols
(reduce
(fn [accum el]
(let [planet-residents (:residents el)]
(+ accum (count planet-residents))))
0
planets)
Interesting reading: https://en.wikipedia.org/wiki/Software_transactional_memory
(def a (atom 0))
a
(deref a)
@a
(swap! a inc)
(swap! a #(* % 3))
@a
(reset! a 32)
@a
;; Talk a little bit on why is atom useful
We will play with clojure.core.async
. The namespace with the functions for async programming and communication in
Clojure.
Interesting reading: https://en.wikipedia.org/wiki/Communicating_sequential_processes and https://arild.github.io/csp-presentation/
Let's start to play with channels.
First we are going to create one, and send a message to hit
; Require the clojure.core.async
(set-env! :dependencies '[[org.clojure/core.async "0.4.474"]])
(require '[clojure.core.async :as async])
(def my-channel (async/chan))
;; you will get stuck here if you run this command
;; (async/>!! my-channel "hello world!")
(async/thread (async/>!! my-channel "hello world"))
(println (async/<!! my-channel))
This was using threads, but can we do better?
Introducing go blocks:
(def my-channel (async/chan))
;; using <!! is incorrect here, we will see why in a minute
(async/go (while true (println (str "In background: " (async/<!! my-channel)))))
(async/>!! my-channel "More hello")
If we replace <!!
by <!
, we are not going to block if no value exists to be consumed, but we are going to park.
Park means that the thread does not need to get stuck waiting for the value to be received. It can do other work and when it has something to consume, it will proceed.
(def my-channel (async/chan))
;; using <! allows our go block to "share" the underlying thread with other go blocks
(async/go (while true (println (async/<! my-channel))))
(async/go (async/>! my-channel "More hello"))
Proof that we are using a low number of threads:
;; this will break your REPL
(doseq [x (range 1 100000)]
(println x)
(async/thread
(while true (Thread/sleep 10000))))
;; this will **NOT** break your REPL
(doseq [x (range 1 100000)]
(println x)
(async/go
(while true (Thread/sleep 10000))))
- EDN
- Homoiconic (big word)
- Extending the language
What is datomic
- Closed source
- Database of facts (not of places)
- Single threaded writer
- Client side query executor
- Plugable storage backend
- Database as a value (not as an external system)
- (minor) Queries are data
Datom - [e a v t op]
- *E - an entity id (E)
- *A - an attribute (A)
- *V - a value for the attribute (V)
- *T - a transaction id (Tx)
- *Op - boolean indicating whether the datom is being added or retracted
;; [e a v t op]
[[101 :eye-colour "brown" t1 true]
[101 :birth-year "1982" t1 true]
[101 :club "benfica" t1 true]
[101 :married true t2 true]
[101 :club "benfica" t3 false]
[101 :club "sporting" t3 true]]
And relation
[[101 :name "Manel" t4 true]
[102 :name "Jakim" t4 true]
[101 :friend 102 t4 true]]
Indices
eavt - Good for all attributes of entity
[[101 :name "Jack" t1 true]
[101 :birth-year "1982" t1 true]
[102 :name "Black" t1 true]
[102 :birth-year "1982" t1 true]]
aevt - Quickly find out entities that have an attribute
[[:name 101 "Jack" t1 true]
[:name 102 "Black" t1 true]
[:birth-year 101 "1982" t1 true]
[:birth-year 102 "1982" t1 true]]
avet - Quick find out entities that have a value.
[[:name "Jack" 101 t1 true]
[:name "Black" 102 t1 true]
[:birth-year "1982" 101 t1 true]
[:birth-year "1982" 102 t1 true]]
vaet - Reverse reference. Only for reference type attributes
;; Note that first is value, entity is third.
[[102 :friend 101 t1 true]
[101 :friend 101 t1 true]]
(range 1 10)
(map inc (range 1 10))
;; Create a inc transducer
(def t1 (map inc))
;; Now I only want the odd numbers
(filter odd? (range 1 10))
;; Create a filter odd transducer
(def t2 (filter odd?))
;;"Apply" transducers (please compare both results)
(transduce (comp t1 t2) conj (range 1 10))
(transduce (comp t2 t1) conj (range 1 10))
;;But it isn't necessary to add them to a list
(transduce (comp t1 t2) str (range 1 10))
- Compiler to javascript
- Repl in browser
- Clojure.spec