Skip to content

Instantly share code, notes, and snippets.

@joninvski
Created November 18, 2019 09:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joninvski/acf6b46dd746720df96850c911b84207 to your computer and use it in GitHub Desktop.
Save joninvski/acf6b46dd746720df96850c911b84207 to your computer and use it in GitHub Desktop.
Clojure workshop

Two hours of ((((()))))

Today's topics:

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.

Part 1 - Boring basics

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)

Part 2 - Understanding the Parenthesis

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)))

Part 3 - Playing with structures

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

Part 4 - Functional transformation in sequences

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)

Part 5 - Software Transactional Memory

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

Part 5.1 - Core.async

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))))

Extra Part 6 - Data as code

  • EDN
  • Homoiconic (big word)
  • Extending the language

Extra Part 7 - Datomic

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]]

Extra Part 8 - Transducers

(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))

Extra Part 9 - Agents

Extra Part 10 - ClojureScript

  • Compiler to javascript
  • Repl in browser

Extra Part 11 - Clojure Spec

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