Skip to content

Latest commit

 

History

History
123 lines (87 loc) · 5.02 KB

2020-06-25.md

File metadata and controls

123 lines (87 loc) · 5.02 KB

Learning Clojure in Public - Week 1 Day 4 (4/35)

Expectations

After a good day on yesterday (Chapter 4 of Clojure from the Ground Up, its exercises and 16 4clojure problems), the expectation for today is more limited as I am constrained by time. I expect to read part of Chapter 6 (skipping Chapter 5, macros, for now as it's not that relevant to the Athens codebase) Whatever I can achieve will be a little victory.

What I learned (mostly from Chapter 6 of Clojure from the Ground Up)

Scope of let

Declaration with let can be used outside of their original expressions as illustrated by this example:

user=> (let [x 1]
         (prn (inc x))
         (prn (inc x)))
2
2

Closures

I was happy to see that closure's, like in JavaScript, also made it to Clojure. Functions will remember the symbols from the time they were constructed. They will reach out to their lexical scope like in this example:

(defn present
  [gift]
  (fn [] gift))

user=> (def red-box (present "plush tiger"))
#'user/red-box
user=> (red-box)
"plush tiger"

In this case, present creates a new function that will always reach for the value of gift, passed at the function creation.

Delays

It is possible to delay a function's execution, by using let. (def later (fn [] (prn "Adding") (+ 1 2))) will only return 3 when the function is called like this (later). There's even a standard macro for this called delay that you can use like this (def later (delay (prn "Adding") (+ 1 2))) and it has to be called with defer, like so (deref later), or with the shorthand @later.

Futures

Futures delegates computation to a different thread, to be evaluated when possible. It's evaluated in parallel. Futures return immediately and give us the identity which will point to the value of the last expression in the future.

user=> (def x (future (prn "hi") (+ 1 2)))
"hi"
#'user/x
user=> (deref x)
3

Evaluation of threads is concurrent and it is possible that expressions will be evaluated out of order. To quote the author, "Futures are the most generic parallel construct in Clojure. You can use futures to do CPU-intensive computation faster, to wait for multiple network requests to complete at once, or to run housekeeping code periodically." This is something that I have not really seen before, coming from JavaScript. The closest in JavaScript would probably using web workers (in the browser).

Promises

You can defer execution, pass a value, and then deref it. It guarantees that any attempt to read the value will wait until the value has been written. We can use promises to synchronize a program which is being evaluated concurrently. A simple example of promise would go like this:

(def box (promise))
(deliver box :maceo)
(deref box) ; returns :maceo

There was an interesting example using a promise and a future, like this:

(def card (promise))
(def dealer (future
              (Thread/sleep 5000)
              (deliver card [(inc (rand-int 13))
                             (rand-nth [:clubs :spades :hearts :diamonds])])))

It indeed delivered on that promise of delivering the... promise. With a (deref card) you fill it and delegate it to another thread. In summary:

  • delays defer evaluation
  • futures parallelize it
  • promises are concurrent "without specifying how the evaluation occurs", we provide the value, when we have it

Vars

So we covered vars back on Tuesday. As a reminder, vars are mutable variables and use the def keyword to be created. They can be updated.

(def x :mouse)
(def box (fn [] x))
(def x :cat)
(box) ; will return :mouse

A reference is the same everywhere and is called a global variable.

Dynamic vars

A dynamic var reference can only be overridden in one particular scope. The convention is to name them with asterisks around their names, like so (def ^:dynamic *board* :maple).

(def ^:dynamic *board* :maple)
(defn cut [] (prn "sawing through" *board*))
(cut) ; "sawing through" :maple

(binding [*board* :cedar] (cut)) ; will return "sawing through" :cedar, this is specific to this scope
(cut) ; otherwise the value remains the same => "sawing through" :maple

Within the binding expression the value of *board* has changed, but it remains the same outside of it. fn and let create immutable lexical scope, binding creates a dynamic scope. The dynamic scope propagates through function calls when the lexical scope is limited to the literal text of the fn or let. When writing actual code, one should use def sparingly.

Takeways

Today I did quite a lot with the time that I had. I had a good introduction to concurrency in Clojure with vars, delay, future and promises. I am looking forward to learning about atoms and refs tomorrow. Let's end the day with a tally of what I completed (which is much more than I anticipated!):

  • Most of Chapter 6 of Clojure From the Group Up
  • 3 4clojure problems
  • Still wrote more ~800 words, only 200 less than previous days