Other Random Staff about clojure

Table of Contents

Some interesting projects:

1 A first try

1.1 Setup

First, install clojure and lein2.

To start a new project:

lein new myproj
cd myproj
lein repl

This will install dependencies and enter repl.

1.2 Type

Since clojure sits on top of JVM, the type seems to be the same, and you can get the type of something by the function type.

user=> (type 3)
;; java.lang.Long
user=> (type :cat)
clojure.lang.Keyword

A list of types

java.lang.Long
(type 3)
clojure.lang.BigInt
(type 5N)
java.lang.Integer
(type (int 0))
java.lang.Short
(type (short 0))
java.lang.Byte
(type (byte 0))
java.lang.Double
(type 1.23)
java.lang.Float
(type (float 1.23))
java.lang.String
(type "cat")
clojure.lang.Symbol
(class 'str)
clojure.lang.Keyword
(type :cat)
clojure.lang.PersistentList
(type '(1 2 3))
clojure.lang.PersistentVector
(type [1 2 3])
clojure.lang.PersistentHashSet
#{:a :b :c}
clojure.lang.PersistentArrayMap
{:name "mittens" :weight 9 :color "black"}

1.3 Function

let
fn
lambda function
def
symbol
defn
define a named function

1.4 Macro

defmacro

1.5 State

1.5.1 delay

Basically this is an abstraction of function: define a function will not evaluate function body. So delay works similar: the call to delay returns an Delay Object, which can be called on by deref to evaluate the body.

(def later (fn [] (prn "Adding") (+ 1 2)))
(later) ;; => print output

(def later (delay (prn "Adding") (+ 1 2)))
(deref later) ;; => print output

The difference between a delay and a function:

  • function evaluate every time it is called
  • delays only evaluate their expressions once. They remember their value, after the first evaluation, and return it for every successive deref.

The delay opeartor: @later is equivalent to (deref later).

1.5.2 Future

This is for parallel.

I don’t need the result of evaluating these expressions yet, but I’d like it later. Could you start working on it in the meantime?

(def x (future (prn "hi") (+ 1 2)))
(deref x) ;; => 3

The body of future is executed in a new thread in parallel! Deref it will get the value of the last expression. Like delays, the expressions are only evaluated once.

1.5.2.1 Atom

The parallel program brings thread-safe problem: modify something at the same time in different threads. Clojure has a atom function to protect a data and ensure its thread-safety.

Use reset! to set value of atom, and use swap! to update.

(def xs #{})
(dotimes [i 10] (future (def xs (conj xs i))))
user=> xs ;; => #{1 4 5 7}

(def xs (atom #{}))
(dotimes [i 10] (future (swap! xs conj i)))
user=> @xs ;; => #{0 1 2 3 4 5 6 7 8 9}
1.5.2.2 Ref

Atom is linearizable, but not serializable: it does not guarantee orders. Ref is serializable.

Use ref-set to set value of a ref, and use alter to update. They must be in a dosync block, and the block order is guaranteed.

user=> (def x (ref 0))
user=> (def y (ref 0))
user=> (dosync
         (ref-set x 1)
         (ref-set y 2))
2
user=> [@x @y]
[1 2]

user=> (def x (ref 1))
user=> (def y (ref 2))
user=> (dosync
         (alter x + 2)
         (alter y inc))
3
user=> [@x @y]
[3 3]

If some of the refs do not need order, you can boost the program by release that, using commute:

user=> (dosync
         (commute x + 2)
         (commute y inc))

Finally, you can use ensure to update one ref using another, guaranteeing order.

user=> (dosync
         (alter x + (ensure y)))

1.5.3 Promise

Delays defer evaluation, and futures parallelize it. What if we wanted to defer something we dont even have yet? To hand someone an empty box and, later, before they open it, sneak in and replacing its contents with an actual gift?

(def box (promise))
(deref box) ;; empty
(deliver box :live-scorpiojns!)
(deref box) ;; => live-scorpiojns!
(deliver box :puppy) ;; => nil
(deref box) ;; => live-scorpiojns!

Some highlights:

  • box contains nothing initially
  • can be delivered
  • cannot be re-delivered

Author: Hebi Li

Created: 2017-12-07 Thu 15:13

Validate