diff --git a/index.html b/index.html index fd99a60..91cb06a 100644 --- a/index.html +++ b/index.html @@ -2,95 +2,24 @@ - + - + -ClojureScript Unraveled - +ClojureScript Unraveled (2nd edition) @@ -222,7 +298,7 @@

2. Introduction

ClojureScript is an implementation of the Clojure programming language that targets JavaScript. Because of this, it can run in many different execution -environments including web browsers, Node.js, io.js and Nashorn.

+environments including web browsers, Node.js, Nashorn (and many other).

Unlike other languages that intend to compile to JavaScript (like TypeScript, @@ -232,15 +308,15 @@

2. Introduction

Another big difference (and in our opinion an advantage) over other languages is -that Clojure is designed to be a guest. It is a language without its own virtual -machine that can be easily adapted to the nuances of its execution environment. This -has the benefit that Clojure (and hence ClojureScript) has access to all the -existing libraries written for the host language.

+that Clojure(Script) is designed to be a guest. It is a language without its own +virtual machine that can be easily adapted to the nuances of its execution +environment. This has the benefit that Clojure (and hence ClojureScript) has +access to all the existing libraries written for the host language.

-

Before we jump in, let us summarize some of the core ideas that ClojureScript brings -to the table. Don’t worry if you don’t understand all of them right now, they’ll -become clear throughout the book.

+

Before we jump in, let us summarize some of the core ideas that ClojureScript +brings to the table. Don’t worry if you don’t understand all of them right now, +they’ll become clear throughout the book.

    @@ -270,15 +346,15 @@

    2. Introduction

    These ideas together have a great influence in the way you design and implement -software, even if you are not using ClojureScript. Functional programming, decoupling -of data (which is immutable) from the operations to transform it, explicit idioms for -managing change over time and polymorphic constructs for programming to abstractions -greatly simplify the systems we write.

    +software, even if you are not using ClojureScript. Functional programming, +decoupling of data (which is immutable) from the operations to transform it, +explicit idioms for managing change over time and polymorphic constructs for +programming to abstractions greatly simplify the systems we write.

    -We can make the same exact software we are making today with dramatically simpler -stuff — dramatically simpler languages, tools, techniques, approaches. +We can make the same exact software we are making today with dramatically +simpler stuff — dramatically simpler languages, tools, techniques, approaches.
    — Rich Hickey @@ -321,7 +397,7 @@

    -
    (+ 1 2 3)
    +
    (+ 1 2 3)
     ;; => 6

    @@ -332,7 +408,7 @@

    -
    (zero? 0)
    +
    (zero? 0)
     ;; => true

    @@ -343,7 +419,7 @@

    -
    '(+ 1 2 3)
    +
    '(+ 1 2 3)
     ;; => (+ 1 2 3)

@@ -354,7 +430,7 @@

-
23
+
23
 +23
 -100
 1.7
@@ -416,54 +492,51 @@ 

3.2.2. Keywords

-
:foobar
+
:foobar
 :2
 :?
-

As you can see, the keywords are all prefixed with :, but this character is only -part of the literal syntax and is not part of the name of the object.

+

As you can see, the keywords are all prefixed with :, but this character is +only part of the literal syntax and is not part of the name of the object.

-

You can also create a keyword by calling the keyword function. Don’t worry if you -don’t understand or are unclear about anything in the following example; +

You can also create a keyword by calling the keyword function. Don’t worry if +you don’t understand or are unclear about anything in the following example; functions are discussed in a later section.

-
(keyword "foo")
+
(keyword "foo")
 ;; => :foo
Namespaced keywords
-

When prefixing keywords with a double colon ::, the keyword will be prepended by the name of the current namespace. -Note that namespacing keywords affects equality comparisons.

+

When prefixing keywords with a double colon ::, the keyword will be prepended +by the name of the current namespace. Note that namespacing keywords affects +equality comparisons.

-
---
-::foo
-;; => :cljs.user/foo
-
+
::foo
+;; => :cljs.user/foo
+
+(= ::foo :foo)
+;; => false
-
-

(= ::foo :foo) -;; ⇒ false ----

-

Another alternative is to include the namespace in the keyword literal, this is useful when creating namespaced keywords -for other namespaces:

+

Another alternative is to include the namespace in the keyword +literal, this is useful when creating namespaced keywords for other +namespaces:

-
---
-:cljs.unraveled/foo
-;; => :cljs.unraveled/foo
----
+
:cljs.unraveled/foo
+;; => :cljs.unraveled/foo
@@ -471,10 +544,8 @@
Namespa
-
---
-(keyword "cljs.unraveled" "foo")
-;; => :cljs.unraveled/foo
----
+
(keyword "cljs.unraveled" "foo")
+;; => :cljs.unraveled/foo
@@ -492,7 +563,7 @@

3.2.3. Symbols

-
sample-symbol
+
sample-symbol
 othersymbol
 f1
 my-special-swap!
@@ -515,7 +586,7 @@

3.2.4. Strings

-
"An example of a string"
+
"An example of a string"
@@ -524,7 +595,7 @@

3.2.4. Strings

-
"This is a multiline
+
"This is a multiline
       string in ClojureScript."
@@ -537,7 +608,7 @@

3.2.5. Characters

-
\a        ; The lowercase a character
+
\a        ; The lowercase a character
 \newline  ; The newline character
@@ -574,7 +645,7 @@
Lists
-
'(1 2 3 4 5)
+
'(1 2 3 4 5)
 '(:foo :bar 2)
@@ -592,7 +663,7 @@
Lists

-
(inc 1)
+
(inc 1)
 ;; => 2
 
 '(inc 1)
@@ -609,7 +680,7 @@ 
Lists
-
(list 1 2 3 4 5)
+
(list 1 2 3 4 5)
 ;; => (1 2 3 4 5)
 
 (list :foo :bar 2)
@@ -635,7 +706,7 @@ 
Vectors
-
[:foo :bar]
+
[:foo :bar]
 [3 4 5 nil]
@@ -649,7 +720,7 @@
Vectors
-
(vector 1 2 3)
+
(vector 1 2 3)
 ;; => [1 2 3]
 
 (vector "blah" 3.5 nil)
@@ -667,7 +738,7 @@ 
Maps
-
{:foo "bar", :baz 2}
+
{:foo "bar", :baz 2}
 {:alphabet [:a :b :c]}
@@ -702,7 +773,7 @@
Sets
-
#{1 2 3 :foo :bar}
+
#{1 2 3 :foo :bar}
 ;; => #{1 :bar 3 :foo 2}
 (set [1 2 1 3 1 4 1 5])
 ;; => #{1 2 3 4 5}
@@ -733,7 +804,7 @@

3.3. Vars

-
(def x 22)
+
(def x 22)
 (def y [1 2 3])
@@ -765,7 +836,7 @@

3.4.1. The

-
(inc 1)
+
(inc 1)
 ;; => 2
@@ -775,7 +846,7 @@

3.4.1. The

-
(+ 1 2 3)
+
(+ 1 2 3)
 ;; => 6
@@ -800,7 +871,7 @@

-
(fn [param1 param2]
+
(fn [param1 param2]
   (/ (+ param1 param2) 2.0))
@@ -809,7 +880,7 @@

-
((fn [x] (* x x)) 5)
+
((fn [x] (* x x)) 5)
 ;; => 25
@@ -821,7 +892,7 @@

-
(def square (fn [x] (* x x)))
+
(def square (fn [x] (* x x)))
 
 (square 12)
 ;; => 144
@@ -833,7 +904,7 @@

-
(defn square
+
(defn square
   "Return the square of a given number."
   [x]
   (* x x))
@@ -858,7 +929,7 @@

-
(defn myinc
+
(defn myinc
   "Self defined version of parameterized `inc`."
   ([x] (myinc x 1))
   ([x increment]
@@ -878,7 +949,7 @@ 

-
(defn my-variadic-set
+
(defn my-variadic-set
   [& params]
   (set params))
 
@@ -934,7 +1005,7 @@ 

-
(def average #(/ (+ %1 %2) 2))
+
(def average #(/ (+ %1 %2) 2))
 
 (average 3 4)
 ;; => 3.5
@@ -945,7 +1016,7 @@

-
(def average-longer (fn [a b] (/ (+ a b) 2)))
+
(def average-longer (fn [a b] (/ (+ a b) 2)))
 
 (average-longer 7 8)
 ;; => 7.5
@@ -966,7 +1037,7 @@

-

3.5.1. Branching with if

+

3.5.1. Branching with if

Let’s start with a basic one: if. In ClojureScript, the if is an expression and not a statement, and it has three parameters: the first one is the condition @@ -991,7 +1062,7 @@

-
(defn discount
+
(defn discount
   "You get 5% discount for ordering 100 or more items"
   [quantity]
   (if (>= quantity 100)
@@ -1011,7 +1082,7 @@ 

-

3.5.2. Branching with cond

+

3.5.2. Branching with cond

Sometimes, the if expression can be slightly limiting because it does not have the "else if" part to add more than one condition. The cond macro comes to the rescue.

@@ -1021,7 +1092,7 @@

-
(defn mypos?
+
(defn mypos?
   [x]
   (cond
     (> x 0) "positive"
@@ -1042,7 +1113,7 @@ 

-
(defn translate-lang-code
+
(defn translate-lang-code
   [code]
   (condp = (keyword code)
     :es "Spanish"
@@ -1063,7 +1134,7 @@ 

-

3.5.3. Branching with case

+

3.5.3. Branching with case

The case branching expression has a similar use as our previous example with condp. The main differences are that case always uses the = predicate/function @@ -1076,7 +1147,7 @@

-
(def valid? #{1 2 3})
+
(def valid? #{1 2 3})
 
 (filter valid? (range 1 10))
 ;; => (1 2 3)
-

This is because set returns the a value if it exists or nil in case contrary:

+

This works because a set returns either the value itself for all contained elements +or nil:

-
(let [x (inc 1)
+
(let [x (inc 1)
       y (+ x 1)]
   (println "Simple message from the body of a let")
   (* x y))
@@ -1177,7 +1249,7 @@ 

3.7.2. Blocks

-
(do
+
(do
   (println "hello world")
   (println "hola mundo")
   (* 3 5) ;; this value will not be returned; it is thrown away
@@ -1209,7 +1281,7 @@ 

3.7.3. Loops

parameters.

-
Looping with loop/recur
+
Looping with loop/recur

Let’s take a look at how to express loops using recursion with the loop and recur forms. loop defines a possibly empty list of bindings (notice the @@ -1221,7 +1293,7 @@

@@ -1408,7 +1480,7 @@
-
;; wrong starting value
+
;; wrong starting value
 (reduce * 0 [3 4 5])
 ;; => 0
 
@@ -1419,7 +1491,7 @@ 
-
for sequence comprehensions
+
for sequence comprehensions

In ClojureScript, the for construct isn’t used for iteration but for generating sequences, an operation also known as "sequence comprehension". In this section @@ -1431,7 +1503,7 @@

-
(for [x [1 2 3]]
+
(for [x [1 2 3]]
   [x (* x x)])
 ;; => ([1 1] [2 4] [3 9])
@@ -1447,7 +1519,7 @@
-
(for [x [1 2 3]
+
(for [x [1 2 3]
       y [4 5]]
   [x y])
 
@@ -1465,7 +1537,7 @@ 
-
(for [x [1 2 3]
+
(for [x [1 2 3]
       y [4 5]
       :let [z (+ x y)]]
   z)
@@ -1478,7 +1550,7 @@ 
-
(for [x [1 2 3]
+
(for [x [1 2 3]
       y [4 5]
       :while (= y 4)]
   [x y])
@@ -1492,7 +1564,7 @@ 
-
(for [x [1 2 3]
+
(for [x [1 2 3]
       y [4 5]
       :when (= (+ x y) 6)]
   [x y])
@@ -1506,7 +1578,7 @@ 
-
(for [x [1 2 3]
+
(for [x [1 2 3]
       y [4 5]
       :let [z (+ x y)]
       :when (= z 6)]
@@ -1527,7 +1599,7 @@ 
-
(doseq [x [1 2 3]
+
(doseq [x [1 2 3]
         y [4 5]
        :let [z (+ x y)]]
   (println x "+" y "=" z))
@@ -1548,7 +1620,7 @@ 
-
(run! println [1 2 3])
+
(run! println [1 2 3])
 ;; 1
 ;; 2
 ;; 3
@@ -1579,7 +1651,7 @@ 

-
(let [xs [1 2 3]
+
(let [xs [1 2 3]
       ys (conj xs 4)]
   (println "xs:" xs)
   (println "ys:" ys))
@@ -1615,7 +1687,7 @@ 

-
(let [xs (list 1 2 3)
+
(let [xs (list 1 2 3)
       ys (cons 0 xs)]
   (println "xs:" xs)
   (println "ys:" ys)
@@ -1650,7 +1722,7 @@ 

-
(first [1 2 3])
+
(first [1 2 3])
 ;; => 1
 
 (rest [1 2 3])
@@ -1663,7 +1735,7 @@ 

-
(seq [])
+
(seq [])
 ;; => nil
 
 (seq [1 2 3])
@@ -1680,7 +1752,7 @@ 

-
(defn print-coll
+
(defn print-coll
   [coll]
   (when (seq coll)
     (println "Saw " (first coll))
@@ -1727,7 +1799,7 @@ 
nil-punning
-
(seq nil)
+
(seq nil)
 ;; => nil
 
 (first nil)
@@ -1749,7 +1821,7 @@ 
-
(map inc [1 2 3])
+
(map inc [1 2 3])
 ;; => (2 3 4)
 
 (map inc #{1 2 3})
@@ -1779,7 +1851,7 @@ 
-
(map (fn [[key value]] (* value value))
+
(map (fn [[key value]] (* value value))
      {:ten 10 :seven 7 :four 4})
 ;; => (100 49 16)
@@ -1790,7 +1862,7 @@
-
(map (fn [value] (* value value))
+
(map (fn [value] (* value value))
      (vals {:ten 10 :seven 7 :four 4}))
 ;; => (100 49 16)
@@ -1802,7 +1874,7 @@
-
(map inc [])
+
(map inc [])
 ;; => ()
 
 (map inc #{})
@@ -1824,7 +1896,7 @@ 
-
(coll? nil)
+
(coll? nil)
 ;; => false
 
 (coll? [1 2 3])
@@ -1843,7 +1915,7 @@ 
-
(seq? nil)
+
(seq? nil)
 ;; => false
 (seqable? nil)
 ;; => false
@@ -1871,7 +1943,7 @@ 
-
(count nil)
+
(count nil)
 ;; => 0
 
 (count [1 2 3])
@@ -1889,7 +1961,7 @@ 
-
(empty nil)
+
(empty nil)
 ;; => nil
 
 (empty [1 2 3])
@@ -1904,7 +1976,7 @@ 
-
(empty? nil)
+
(empty? nil)
 ;; => true
 
 (empty? [])
@@ -1925,7 +1997,7 @@ 
-
(range 5)
+
(range 5)
 ;; => (0 1 2 3 4)
 (range 1 10)
 ;; => (1 2 3 4 5 6 7 8 9)
@@ -1980,7 +2052,7 @@ 
Laziness
-
(take-while (fn [x] (< (+ (* 2 x x) 5) 100))
+
(take-while (fn [x] (< (+ (* 2 x x) 5) 100))
             (range 0 100))
 ;; => (0 1 2 3 4 5 6)
@@ -2011,7 +2083,7 @@
Lists
-
(cons 0 (cons 1 (cons 2 ())))
+
(cons 0 (cons 1 (cons 2 ())))
 ;; => (0 1 2)
@@ -2023,7 +2095,7 @@
Lists
-
(cons 0 '(1 2))
+
(cons 0 '(1 2))
 ;; => (0 1 2)
@@ -2033,7 +2105,7 @@
Lists
-
(conj '(1 2) 0)
+
(conj '(1 2) 0)
 ;; => (0 1 2)
@@ -2051,7 +2123,7 @@
Lists
-
(def list-stack '(0 1 2))
+
(def list-stack '(0 1 2))
 
 (peek list-stack)
 ;; => 0
@@ -2090,7 +2162,7 @@ 
Vectors
-
(vector? [0 1 2])
+
(vector? [0 1 2])
 ;; => true
 
 (vector 0 1 2)
@@ -2107,7 +2179,7 @@ 
Vectors
-
(conj [0 1] 2)
+
(conj [0 1] 2)
 ;; => [0 1 2]
@@ -2118,7 +2190,7 @@
Vectors
-
(nth [0 1 2] 0)
+
(nth [0 1 2] 0)
 ;; => 0
@@ -2131,7 +2203,7 @@
Vectors
-
(assoc ["cero" "uno" "two"] 2 "dos")
+
(assoc ["cero" "uno" "two"] 2 "dos")
 ;; => ["cero" "uno" "dos"]
@@ -2141,7 +2213,7 @@
Vectors
-
(assoc ["cero" "uno" "dos"] 3 "tres")
+
(assoc ["cero" "uno" "dos"] 3 "tres")
 ;; => ["cero" "uno" "dos" "tres"]
 
 (assoc ["cero" "uno" "dos"] 4 "cuatro")
@@ -2156,7 +2228,7 @@ 
Vectors
-
(["cero" "uno" "dos"] 0)
+
(["cero" "uno" "dos"] 0)
 ;; => "cero"
 
 (["cero" "uno" "dos"] 2)
@@ -2173,7 +2245,7 @@ 
Vectors
-
(def vector-stack [0 1 2])
+
(def vector-stack [0 1 2])
 
 (peek vector-stack)
 ;; => 2
@@ -2200,7 +2272,7 @@ 
Vectors
-
(map inc [0 1 2])
+
(map inc [0 1 2])
 ;; => (1 2 3)
 
 (type (map inc [0 1 2]))
@@ -2228,7 +2300,7 @@ 
Maps
-
(map? {:name "Cirilla"})
+
(map? {:name "Cirilla"})
 ;; => true
 
 (hash-map :name "Cirilla")
@@ -2245,7 +2317,7 @@ 
Maps
-
(def ciri {:name "Cirilla"})
+
(def ciri {:name "Cirilla"})
 
 (conj ciri [:surname "Fiona"])
 ;; => {:name "Cirilla", :surname "Fiona"}
@@ -2266,7 +2338,7 @@ 
Maps
-
(assoc {:name "Cirilla"} :surname "Fiona")
+
(assoc {:name "Cirilla"} :surname "Fiona")
 ;; => {:name "Cirilla", :surname "Fiona"}
 (assoc {:name "Cirilla"} :name "Alfonso")
 ;; => {:name "Alfonso"}
@@ -2281,7 +2353,7 @@ 
Maps
-
({:name "Cirilla"} :name)
+
({:name "Cirilla"} :name)
 ;; => "Cirilla"
 
 ({:name "Cirilla"} :surname)
@@ -2295,7 +2367,7 @@ 
Maps
-
(def sm (sorted-map :c 2 :b 1 :a 0))
+
(def sm (sorted-map :c 2 :b 1 :a 0))
 ;; => {:a 0, :b 1, :c 2}
 
 (keys sm)
@@ -2311,7 +2383,7 @@ 
Maps
-
(defn reverse-compare [a b] (compare b a))
+
(defn reverse-compare [a b] (compare b a))
 
 (def sm (sorted-map-by reverse-compare :a 0 :b 1 :c 2))
 ;; => {:c 2, :b 1, :a 0}
@@ -2330,7 +2402,7 @@ 
Sets
-
(set? #{\a \e \i \o \u})
+
(set? #{\a \e \i \o \u})
 ;; => true
 
 (set [1 1 2 3])
@@ -2343,7 +2415,7 @@ 
Sets
-
#{1 1 2 3}
+
#{1 1 2 3}
 ;; clojure.lang.ExceptionInfo: Duplicate key: 1
@@ -2356,7 +2428,7 @@
Sets
-
(require '[clojure.set :as s])
+
(require '[clojure.set :as s])
 
 (def danish-vowels #{\a \e \i \o \u   })
 ;; => #{"a" "e" "å" "æ" "i" "o" "u" "ø"}
@@ -2385,7 +2457,7 @@ 
Sets
-
(def spanish-vowels #{\a \e \i \o \u})
+
(def spanish-vowels #{\a \e \i \o \u})
 ;; => #{"a" "e" "i" "o" "u"}
 
 (def danish-vowels (conj spanish-vowels   ))
@@ -2402,7 +2474,7 @@ 
Sets
-
(def vowels #{\a \e \i \o \u})
+
(def vowels #{\a \e \i \o \u})
 ;; => #{"a" "e" "i" "o" "u"}
 
 (get vowels \b)
@@ -2428,7 +2500,7 @@ 
Sets
-
(def unordered-set #{[0] [1] [2]})
+
(def unordered-set #{[0] [1] [2]})
 ;; => #{[0] [2] [1]}
 
 (seq unordered-set)
@@ -2451,7 +2523,7 @@ 
Queues
-
(def pq #queue [1 2 3])
+
(def pq #queue [1 2 3])
 ;; => #queue [1 2 3]
@@ -2460,7 +2532,7 @@
Queues
-
(def pq #queue [1 2 3])
+
(def pq #queue [1 2 3])
 ;; => #queue [1 2 3]
 
 (conj pq 4 5)
@@ -2474,7 +2546,7 @@ 
Queues
-
(def pq #queue [1 2 3])
+
(def pq #queue [1 2 3])
 ;; => #queue [1 2 3]
 
 (peek pq)
@@ -2510,7 +2582,7 @@ 

3.9

+

If the map you want to destructure has namespaced keywords as keys, you also can +do it using the keyword syntax inside :keys vector:

+
+
+
+
(let [{:keys [::name ::surname]} {::name "Cirilla" ::surname "Fiona"}]
+  [name surname])
+;; => ["Cirilla" "Fiona"]
+
+
+

An interesting property of destructuring is that we can nest destructuring forms arbitrarily, which makes code that accesses nested data on a collection very easy to understand, as it mimics the collection’s structure:

-
(let [{[fst snd] :languages} {:languages ["ClojureScript" "Clojure"]}]
+
(let [{[fst snd] :languages} {:languages ["ClojureScript" "Clojure"]}]
   [snd fst])
 ;; => ["Clojure" "ClojureScript"]
@@ -2718,13 +2801,14 @@

3.9

3.10. Threading Macros

-

Threading macros, also known as arrow functions, enables one to write more readable code -when multiple nested function calls are performed.

+

Threading macros, also known as arrow functions, enables one to write +more readable code when multiple nested function calls are performed.

-

Imagine you have (f (g (h x))) where a function f receives as its first parameter the -result of executing function g, repeated multiple times. With the most basic threading -macro you can convert that into (-> x (h) (g) (f)) which is easier to read.

+

Imagine you have (f (g (h x))) where a function f receives as its first +parameter the result of executing function g, repeated multiple times. With +the most basic threading macro you can convert that into (-> x (h) (g) +(f)) which is easier to read.

The result is syntactic sugar, because the arrow functions are defined as macros @@ -2732,22 +2816,22 @@

3.10. Threadi automatically converted to (f (g (h x))) at compile time.

-

Take note that the parenthesis on h, g and f are optional, and can be ommited: -(f (g (h x))) is the same as (-> x h g f).

+

Take note that the parenthesis on h, g and f are optional, and can be +ommited: (f (g (h x))) is the same as (-> x h g f).

-

3.10.1. The thread-first macro (->)

+

3.10.1. -> (thread-first macro)

This is called thread first because it threads the first argument throught the different expressions as first arguments.

-

Using a more concrete example, this is how the code looks without using threading -macros:

+

Using a more concrete example, this is how the code looks without using +threading macros:

-

3.10.2. The thread-last macro (->>)

+

3.10.2. ->> (thread-last macro)

The main difference between the thread-last and thread-first macros is that instead of threading the first argument given as the first argument on the following expresions, @@ -2783,7 +2868,7 @@

-
(def numbers [1 2 3 4 5 6 7 8 9 0])
+
(def numbers [1 2 3 4 5 6 7 8 9 0])
 
 (take 2 (filter odd? (map inc numbers)))
 ;; => (3 5)
@@ -2794,7 +2879,7 @@

-
(->> numbers
+
(->> numbers
      (map inc)
      (filter odd?)
      (take 2))
@@ -2808,24 +2893,24 @@ 

-

3.10.3. The thread-as macro (as->)

+

3.10.3. as-> (thread-as macro)

Finally, there are cases where neither -> nor ->> are applicable. In these -cases, you’ll need to use as->, the more flexible alternative, that allows you to -thread into any argument position, not just the first or last.

+cases, you’ll need to use as->, the more flexible alternative, that allows +you to thread into any argument position, not just the first or last.

It expects two fixed arguments and an arbitrary number of expressions. As with -->, the first argument is a value to be threaded through the following forms. The -second argument is the name of a binding. In each of the subsequent forms, the bound -name can be used for the prior expression’s result.

+->, the first argument is a value to be threaded through the following +forms. The second argument is the name of a binding. In each of the subsequent +forms, the bound name can be used for the prior expression’s result.

Let’s see an example:

-
(as-> numbers $
+
(as-> numbers $
   (map inc $)
   (filter odd? $)
   (first $)
@@ -2835,7 +2920,7 @@ 

-

3.10.4. The thread-some macros (some-> and some->>)

+

3.10.4. some->, some->> (thread-some macros)

Two of the more specialized threading macros that ClojureScript comes with. They work in the same way as their analagous -> and ->> macros with the additional @@ -2847,7 +2932,7 @@

-
(some-> (rand-nth [1 nil])
+
(some-> (rand-nth [1 nil])
         (inc))
 ;; => 2
 
@@ -2861,14 +2946,14 @@ 

-

3.10.5. The thread-cond macros (cond-> and cond->>)

+

3.10.5. cond->, cond->> (thread-cond macros)

The cond-> and cond->> macros are analgous to -> and ->> that offers the ability to conditionally skip some steps from the pipeline. Let see an example:

-
(defn describe-number
+
(defn describe-number
   [n]
   (cond-> []
     (odd? n) (conj "odd")
@@ -2914,7 +2999,7 @@ 

3.11. R they are placed in files with .cljc extension.

-

3.11.1. Standard (#?)

+

3.11.1. Standard (#?)

There are two types of reader conditionals, standard and splicing. The standard reader conditional behaves similarly to a traditional cond and the syntax looks @@ -2922,10 +3007,10 @@

3.11.1. S

-
(defn parse-int
+
(defn parse-int
   [v]
-  #?(:clj  (Integer/parseInt s)
-     :cljs (js/parseInt s)))
+ #?(:clj (Integer/parseInt v) + :cljs (js/parseInt v)))
-

3.11.2. Splicing (#?@)

+

3.11.2. Splicing (#?@)

The splicing reader conditional works in the same way as the standard and allows splice lists into the containing form. The #?@ reader macro is used for that @@ -2945,7 +3030,7 @@

3.11.2. S

-
(def x "hello")
+
(def x "hello")
 ;; => #'cljs.user/x
@@ -3059,7 +3144,7 @@

-
(ns myapp.main
+
(ns myapp.main
   (:require myapp.core
             clojure.string))
 
@@ -3079,7 +3164,7 @@ 

+
+
+
(ns myapp.main
+  (:require [myapp.core :as c]))
+
+::c/foo
+;; => :myapp.core/foo
+
+
+
+

In the same way, you can namespace all the keys on the moment of creation of a +map:

+
+
+
+
(def x #::c {:a 1})
+
+x
+;; => #:myapp.core{:a 1}
+
+(::c/a x)
+;; => 1
+
+
+

Additionally, ClojureScript offers a simple way to refer to specific vars or functions from a concrete namespace using the :refer directive, followed by a sequence of symbols that will refer to vars in the namespace. Effectively, it is as @@ -3096,7 +3209,7 @@

-
(ns myapp.main
+
(ns myapp.main
   (:require [clojure.string :refer [upper-case]]))
 (upper-case x)
 ;; => "HELLO"
@@ -3114,7 +3227,7 @@

-
(ns myapp.testproto)
+
(ns myapp.testproto)
 
 (defprotocol IProtocolName
   "A docstring describing the protocol."
@@ -3222,7 +3335,7 @@ 

3.13.1. Protocols

-
(defprotocol IInvertible
+
(defprotocol IInvertible
   "This is a protocol for data types that are 'invertible'"
   (invert [this] "Invert the given item."))
@@ -3239,7 +3352,7 @@
-
(extend-type TypeA
+
(extend-type TypeA
   ProtocolA
   (function-from-protocol-a [this]
     ;; implementation here
@@ -3263,7 +3376,7 @@ 
-
(extend-type string
+
(extend-type string
   IInvertible
   (invert [this] (apply str (reverse this))))
 
@@ -3293,7 +3406,7 @@ 
-
(invert "abc")
+
(invert "abc")
 ;; => "cba"
 
 (invert 0)
@@ -3312,7 +3425,7 @@ 
-
(defn make-user
+
(defn make-user
   [firstname lastname]
   (User. firstname lastname))
@@ -3784,7 +3897,7 @@

3.14.2. Defrecord

-
(defrecord User [firstname lastname])
+
(defrecord User [firstname lastname])
@@ -3796,7 +3909,7 @@

3.14.2. Defrecord

-
(def person (User. "Yennefer" "of Vengerberg"))
+
(def person (User. "Yennefer" "of Vengerberg"))
 
 (:firstname person)
 ;; => "Yennefer"
@@ -3810,7 +3923,7 @@ 

3.14.2. Defrecord

-
(map? person)
+
(map? person)
 ;; => true
@@ -3819,7 +3932,7 @@

3.14.2. Defrecord

-
(def person2 (assoc person :age 92))
+
(def person2 (assoc person :age 92))
 
 (:age person2)
 ;; => 92
@@ -3837,7 +3950,7 @@

3.14.2. Defrecord

-
(def plain-person {:firstname "Yennefer", :lastname "of Vengerberg"})
+
(def plain-person {:firstname "Yennefer", :lastname "of Vengerberg"})
 
 (plain-person :firstname)
 ;; => "Yennefer"
@@ -3855,7 +3968,7 @@ 

3.14.2. Defrecord

-
(def cirilla (->User "Cirilla" "Fiona"))
+
(def cirilla (->User "Cirilla" "Fiona"))
 (def yen (map->User {:firstname "Yennefer"
                      :lastname "of Vengerberg"}))
@@ -3870,7 +3983,7 @@

3

-
(defn user
+
(defn user
   [firstname lastname]
   (reify
     IUser
@@ -3929,7 +4042,7 @@ 

3.14.5. Specify

-
(def obj #js {})
+
(def obj #js {})
 
 (specify! obj
   IUser
@@ -3946,7 +4059,7 @@ 

3.14.5. Specify

-
(def a {})
+
(def a {})
 
 (def b (specify a
          IUser
@@ -4023,7 +4136,7 @@ 
A
-
(js/parseInt "222")
+
(js/parseInt "222")
 ;; => 222
@@ -4038,7 +4151,7 @@
C
-
(new js/RegExp "^foo$")
+
(new js/RegExp "^foo$")
-
(js/RegExp. "^foo$")
+
(js/RegExp. "^foo$")
@@ -4068,7 +4181,7 @@
-
(def clj-map {:country {:code "FR" :name "France"}})
+
(def clj-map {:country {:code "FR" :name "France"}})
 ;; => {:country {:code "FR", :name "France"}}
-(:code (:country clj-map)
+(:code (:country clj-map))
 ;; => "FR"
 
 (def js-obj #js {:country {:code "FR" :name "France"}})
@@ -4213,7 +4326,7 @@ 
Conversions
-
(clj->js {:foo {:bar "baz"}})
+
(clj->js {:foo {:bar "baz"}})
 ;; => #js {:foo #js {:bar "baz"}}
 (js->clj #js {:country {:code "FR" :name "France"}}))
 ;; => {"country" {:code "FR", :name "France"}}
@@ -4225,7 +4338,7 @@
Conversions
-
(into-array ["France" "Korea" "Peru"])
+
(into-array ["France" "Korea" "Peru"])
 ;; => #js ["France" "Korea" "Peru"]
@@ -4240,7 +4353,7 @@
Arrays
Creating a preallocated array with length 10
-
(def a (make-array 10))
+
(def a (make-array 10))
 ;; => #js [nil nil nil nil nil nil nil nil nil nil]
@@ -4250,7 +4363,7 @@
Arrays
-
(count a)
+
(count a)
 ;; => 10
@@ -4260,7 +4373,7 @@
Arrays
-
(aset a 0 2)
+
(aset a 0 2)
 ;; => 2
 a
 ;; => #js [2 nil nil nil nil nil nil nil nil nil]
@@ -4271,7 +4384,7 @@
Arrays
-
(aget a 0)
+
(aget a 0)
 ;; => 2
@@ -4281,7 +4394,7 @@
Arrays
-
(def b #js {:hour 16})
+
(def b #js {:hour 16})
 ;; => #js {:hour 16}
 
 (aget b "hour")
@@ -4333,7 +4446,7 @@ 

3.16.2. Atoms

-
(def ciri (atom {:name "Cirilla" :lastname "Fiona" :age 20}))
+
(def ciri (atom {:name "Cirilla" :lastname "Fiona" :age 20}))
 ;; #<Atom: {:name "Cirilla", :lastname "Fiona", :age 20}>
 
 (deref ciri)
@@ -4349,7 +4462,7 @@ 

3.16.2. Atoms

-
(swap! ciri update :age inc)
+
(swap! ciri update :age inc)
 ;; {:name "Cirilla", :lastname "Fiona", :age 21}
 
 @ciri
@@ -4361,7 +4474,7 @@ 

3.16.2. Atoms

-
(reset! ciri {:name "Cirilla", :lastname "Fiona", :age 22})
+
(reset! ciri {:name "Cirilla", :lastname "Fiona", :age 22})
 ;; {:name "Cirilla", :lastname "Fiona", :age 22}
 
 @ciri
@@ -4379,7 +4492,7 @@ 
Observation
-
(def a (atom))
+
(def a (atom))
 
 (add-watch a :logger (fn [key the-atom old-value new-value]
                        (println "Key:" key "Old:" old-value "New:" new-value)))
@@ -4413,7 +4526,7 @@ 

3.16.3. Volatiles

-
(def ciri (volatile! {:name "Cirilla" :lastname "Fiona" :age 20}))
+
(def ciri (volatile! {:name "Cirilla" :lastname "Fiona" :age 20}))
 ;; #<Volatile: {:name "Cirilla", :lastname "Fiona", :age 20}>
 
 (volatile? ciri)
@@ -4441,40 +4554,9 @@ 

3.16.3. Volatiles

4. Tooling & Compiler

-

This chapter will cover a little introduction to existing tooling for making things -easy when developing using ClojureScript. It will cover:

-
-
-
    -
  • -

    Using the REPL

    -
  • -
  • -

    Leiningen and cljsbuild

    -
  • -
  • -

    Google Closure Library

    -
  • -
  • -

    Modules

    -
  • -
  • -

    Unit testing

    -
  • -
  • -

    Library development

    -
  • -
  • -

    Browser based development

    -
  • -
  • -

    Server based development

    -
  • -
-
-
-

Unlike the previous chapter, this chapter intends to tell different stories each -independent of the other.

+

This chapter will cover a little introduction to existing tooling for making +things easy when developing using ClojureScript. Unlike the previous chapter, +this chapter intends to tell different stories each independent of the other.

-

There are others, such as Rhino (JDK 6+), Nashorn (JDK 8), QtQuick (QT),…​ but none -of them have significant differences from the first two. So, ClojureScript at the -moment may compile code to run in the browser or in nodejs-like environments out of -the box.

+

There are others, such as Rhino (JDK 6+), Nashorn (JDK 8+), QtQuick (QT),…​ but +none of them have significant differences from the first two. So, +ClojureScript at the moment may compile code to run in the browser or in +nodejs-like environments out of the box.

4.1.2. Download the compiler

-

Although the ClojureScript is self hosted, the best way to use it is just using the -JVM implementation. To use it, you should have jdk8 installed. ClojureScript itself -only requires JDK 7, but the standalone compiler that we are going to use in this -chapter requires JDK 8, which can be found at -http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html

-
-
-

You can download the latest ClojureScript compiler using wget:

+

The fastest way to obtain all the tooling needed for ClojureScript compilaition is +installing the Clojure CLI Tools:

-
wget https://github.com/clojure/clojurescript/releases/download/r1.9.36/cljs.jar
+
curl -O https://download.clojure.org/install/linux-install-1.10.1.462.sh
+chmod +x linux-install-1.10.1.462.sh
+sudo ./linux-install-1.10.1.462.sh
-

The ClojureScript compiler is packaged in a standalone executable jar file, so this -is the only file (along with JDK 8) that you need to compile your ClojureScript -source code to JavaScript.

+

If you are using other operating system other than linux, refer to +https://clojure.org/guides/getting_started page.

+
+
+

Although the ClojureScript is self hosted, in this book we will use +the JVM implementation, with in turn requires JDK8 or JDK11 +installed. You can obtain it using your distribution default package +manager or download it from +Oracle +or Azul

+
+
+
Example installing it using a apt in a debian-like distribution
+
+
sudo apt-get install openjdk-8-jdk rlwrap
+
@@ -4551,8 +4642,8 @@

4.1.3.

-
nvm install v6.2.0
-nvm alias default v6.2.0
+
nvm install v10.16.0
+nvm alias default v10.16.0
-
$ node --version
-v6.2.0
+
$ node --version
+v10.16.0
@@ -4584,7 +4675,7 @@
-
(require '[cljs.build.api :as b])
+
(require '[cljs.build.api :as b])
 
-(b/build "src"
- {:main 'myapp.core
-  :output-to "main.js"
-  :output-dir "out"
-  :target :nodejs
-  :verbose true})
+(b/build "src" {:main 'myapp.core + :output-to "main.js" + :output-dir "out" + :target :nodejs + :verbose true})
@@ -4698,7 +4799,7 @@

-
mkdir -p mywebapp/src/mywebapp
+
mkdir -p mywebapp/src/mywebapp
 touch mywebapp/src/mywebapp/core.cljs
@@ -4707,7 +4808,7 @@

-
mywebapp
+
mywebapp
 └── src
     └── mywebapp
         └── core.cljs
@@ -4718,7 +4819,7 @@

-
(ns mywebapp.core)
+
(ns mywebapp.core)
 
 (enable-console-print!)
 
@@ -4733,19 +4834,18 @@ 

Compile the example application

In order to compile the source code to run properly in a browser, overwrite the -mywebapp/build.clj file with the following content:

+mywebapp/build.clj file with the following content:

-
(require '[cljs.build.api :as b])
+
(require '[cljs.build.api :as b])
 
-(b/build "src"
- {:output-to "main.js"
-  :output-dir "out/"
-  :source-map true
-  :main 'mywebapp.core
-  :verbose true
-  :optimizations :none})
+(b/build "src" {:output-to "main.js" + :source-map true + :output-dir "out/" + :main 'mywebapp.core + :verbose true + :optimizations :none})
@@ -4800,7 +4900,7 @@
-
<!DOCTYPE html>
+
<!DOCTYPE html>
 <html>
   <header>
     <meta charset="utf-8" />
@@ -4823,46 +4923,78 @@ 
4.1.5. Watch process

You may have already noticed the slow startup time of the ClojureScript -compiler. To solve this, the ClojureScript standalone compiler comes with a tool to -watch for changes in your source code, and re-compile modified files as soon as they -are written to disk

+compiler. To solve this, the ClojureScript standalone compiler comes with a +tool to watch for changes in your source code, and re-compile modified files as +soon as they are written to disk.

-

Start by creating another build script, but this time name it watch.clj:

+

Let’s start converting our build.clj script to something that can accept +arguments and execute different tasks. Let’s create a tools.clj script file +with the following content:

-
(require '[cljs.build.api :as b])
+
(require '[cljs.build.api :as b])
 
-(b/watch "src"
- {:output-to "main.js"
-  :output-dir "out/"
-  :source-map true
-  :main 'mywebapp.core
-  :optimizations :none})
+(defmulti task first) + +(defmethod task :default + [args] + (let [all-tasks (-> task methods (dissoc :default) keys sort) + interposed (->> all-tasks (interpose ", ") (apply str))] + (println "Unknown or missing task. Choose one of:" interposed) + (System/exit 1))) + +(def build-opts + {:output-to "main.js" + :source-map true + :output-dir "out/" + :main 'mywebapp.core + :verbose true + :optimizations :none}) + +(defmethod task "build" + [args] + (b/build "src" build-opts)) + +(defmethod task "watch" + [args] + (b/watch"src" build-opts)) + +(task *command-line-args*)
-

Now, execute the script just like you have in previous sections:

+

Now you can start the watch process with the following command:

-
$ java -cp ../cljs.jar:src clojure.main watch.clj
-Building ...
-Reading analysis cache for jar:file:/home/niwi/cljsbook/playground/cljs.jar!/cljs/core.cljs
-Compiling src/mywebapp/core.cljs
-Compiling out/cljs/core.cljs
-Using cached cljs.core out/cljs/core.cljs
-... done. Elapsed 0.754487937 seconds
-Watching paths: /home/niwi/cljsbook/playground/mywebapp/src
+
clojure tools.clj watch

Go back to the mywebapp.core namespace, and change the print text to "Hello World, Again!". You’ll see that the file src/mywebapp/core.cljs the file is immediately recompiled, and if you reload index.html in your browser the new text is displayed -in the developer console. Another advantage of this method is that it gives a little -bit more output.

+in the developer console.

+
+
+

You also can start the simple build with:

+
+
+
+
clojure tools.clj build
+
+
+
+

And finally, if you execute the build.clj script with no params, a help message +with available "tasks" will be printed:

+
+
+
+
$ clojure tools.clj
+Unknown or missing task. Choose one of: build, watch
+
@@ -4988,55 +5120,84 @@

4.2.1. Introducti

4.2.2. Nashorn REPL

The Nashorn REPL is the easiest and perhaps most painless REPL environment because it -does not require any special stuff, just the JVM (JDK 8) that you have used in -previous examples for running the ClojureScript compiler.

+does not require any special stuff.

-

Let’s start creating the repl.clj file with the following content:

+

Let’s start creating a new script file for our repl playground called +tools.clj in a new directory (in our case repl_playground/tools.clj):

-
(require '[cljs.repl]
-         '[cljs.repl.nashorn])
+
(require '[cljs.repl :as repl])
+(require '[cljs.repl.nashorn :as nashorn])
+
+(defmulti task first)
+
+(defmethod task :default
+  [args]
+  (let [all-tasks  (-> task methods (dissoc :default) keys sort)
+        interposed (->> all-tasks (interpose ", ") (apply str))]
+    (println "Unknown or missing task. Choose one of:" interposed)
+    (System/exit 1)))
+
+(defmethod task "repl:nashorn"
+  [args]
+  (repl/repl (nashorn/repl-env)
+             :output-dir "out/nashorn"
+             :cache-analysis true))
 
-(cljs.repl/repl
- (cljs.repl.nashorn/repl-env)
- :output-dir "out"
- :cache-analysis true)
+(task *command-line-args*)
-

Then, execute the following command to get the REPL up and running:

+

Create the repl_playground/deps.edn file with the following content (identical +from previous examples):

-
$ java -cp cljs.jar:src clojure.main repl.clj
-To quit, type: :cljs/quit
-cljs.user=> (+ 1 2)
-3
-
+
{:deps {org.clojure/clojurescript {:mvn/version "1.10.520"}
+        org.clojure/clojure {:mvn/version "1.10.1"}}
+ :paths ["src"]}
-
-

You may have noticed that the REPL does not have support for history and other -shell-like facilities. This is because the default REPL does not comes with -"readline" support. But this problem can be solved using a simple tool named rlwrap -which you should be able to find find with the package manager of your operating -system (e.g. for Ubuntu, type sudo apt install -y rlwrap to install).

-

The rlwrap tool gives the REPL "readline" capability, and will allow you to have -command history, code navigation, and other shell-like utilities that will make your -REPL experience much more pleasant. To use it, just prepend it to the previous -command that we used to start the REPL:

+

And now, we can execute the REPL:

-
$ rlwrap java -cp cljs.jar:src clojure.main repl.clj
-To quit, type: :cljs/quit
-cljs.user=> (+ 1 2)
-3
+
$ clj tools.clj repl:nashorn
+ClojureScript 1.10.520
+cljs.user=> (prn "Hello world")
+"Hello world"
+nil
+
+

You may have noticed that in this example we have used clj command instead of +clojure. That two commands are practically identical, the main difference is +that clj executes clojure command wrapped in rlwrap. The rlwrap tool +gives the "readline" capabilities which enables history, code navigation, and +other shell-like features that will make your REPL experience much more +pleasant.

+
+
+

If you don’t have installed it previously, you can install it with sudo apt +install -y rlwrap.

+
+
+ + + + + +
+
Note
+
+This is a basic repl, in the following chapters we will explain how to +have a more advanced repl experience with code-highlighting, code-completion and +multiline edition. +
+

@@ -5083,38 +5245,37 @@

4.2.4. Browser REPL

-

Let’s start by creating a file named brepl.clj with the following content:

+

Let’s start by adding the following content to the tools.clj script file:

-
(require
-  '[cljs.build.api :as b]
-  '[cljs.repl :as repl]
-  '[cljs.repl.browser :as browser])
+
(require '[cljs.build.api :as b])
+(require '[cljs.repl.browser :as browser])
 
-(b/build "src"
- {:output-to "main.js"
-  :output-dir "out/"
-  :source-map true
-  :main 'myapp.core
-  :verbose true
-  :optimizations :none})
+(defmethod task "repl:browser"
+  [args]
+  (println "Building...")
+  (b/build "src"
+           {:output-to "out/browser/main.js"
+            :output-dir "out/browser"
+            :source-map true
+            :main 'myapp.core
+            :optimizations :none})
 
-(repl/repl (browser/repl-env)
-  :output-dir "out")
+ (println "Launching REPL...") + (repl/repl (browser/repl-env :port 9001) + :output-dir "out/browser"))
-

This script builds the source, just as we did earlier, and then starts the REPL.

-
-
-

But the browser REPL also requires that some code be executed in the browser before -the REPL gets working. To do that, just re-create the application structure very -similar to the one that we have used in previous sections:

+

The main difference with the previous examples, is that browser REPL requires +that some code be execution in the browser before the REPL gets working. To do +that, just re-create the application structure very similar to the one that we +have used in previous sections:

-
mkdir -p src/myapp
+
mkdir -p src/myapp
 touch src/myapp/core.cljs
@@ -5123,11 +5284,11 @@

4.2.4. Browser REPL
-

And finally, create the missing index.html file that is going to be used as the -entry point for running the browser side code of the REPL:

+

And finally, create the missing index.html file that is going to be used as +the entry point for running the browser side code of the REPL:

-
<!DOCTYPE html>
+
<!DOCTYPE html>
 <html>
   <header>
     <meta charset="utf-8" />
     <title>Hello World from ClojureScript</title>
   </header>
   <body>
-    <script src="main.js"></script>
+    <script src="out/browser/main.js"></script>
   </body>
 </html>

Well, that was a lot of setup! But trust us, it’s all worth it when you see it in -action. To do that, just execute the brepl.clj in the same way that we have done +action. To do that, just execute the tools.clj in the same way that we have done it in previous examples:

-
$ rlwrap java -cp cljs.jar:src clojure.main brepl.clj
-Compiling client js ...
-Waiting for browser to connect ...
+
$ clj tools.clj repl:browser
+Building...
+Launching REPL...
+ClojureScript 1.10.520
+cljs.user=>
-

And finally, open your favourite browser and go to http://localhost:9000/. Once the +

And finally, open your favourite browser and go to http://localhost:9001/. Once the page is loaded (the page will be blank), switch back to the console where you have run the REPL and you will see that it is up and running:

+
+

4.2.5. Rebel Readline (REPL library)

+
+

This is a library that adds more advanced features to the Clojure(Script) +builtin REPL and enables code-highlighting, code-completion and multiline +edition.

+
+
+

Let’s start adding rebel dependency into deps.edn file:

+
+
+
+
{:deps {org.clojure/clojurescript {:mvn/version "1.10.520"}
+        org.clojure/clojure {:mvn/version "1.10.1"}
+        com.bhauman/rebel-readline-cljs {:mvn/version "0.1.4"}
+        com.bhauman/rebel-readline {:mvn/version "0.1.4"}}
+ :paths ["src"]}
+
+
+
+

And adding the followin code to the tools.clj script file:

+
+
+
+
(require '[rebel-readline.core]
+         '[rebel-readline.clojure.main]
+         '[rebel-readline.clojure.line-reader]
+         '[rebel-readline.cljs.service.local]
+         '[rebel-readline.cljs.repl])
+
+(defmethod task "repl:rebel:node"
+  [args]
+  (rebel-readline.core/with-line-reader
+    (rebel-readline.clojure.line-reader/create
+     (rebel-readline.cljs.service.local/create))
+    (repl/repl (node/repl-env)
+               :prompt (fn [])
+               :read (rebel-readline.cljs.repl/create-repl-read)
+               :output-dir "out/nodejs"
+               :cache-analysis true)))
+
+
+
+

And start the REPL:

+
+
+
+
$ clojure tools.clj repl:rebel:node
+ClojureScript 1.10.520
+cljs.user=> (println
+cljs.core/println: ([& objs])
+
+
+
+

You can find that while you writing in the repl, it automatically suggest and +shows se function signature that you want to execute.

+
+
+

You can find more information about all rebel-readline capabilities on +https://github.com/bhauman/rebel-readline

+
@@ -5202,7 +5443,7 @@

4.3. Th

4.4. Dependency management

-

Until now, we have used the builtin ClojureScript toolchain to compile our source -files to JavaScript. This is the minimal setup required for working with and -understanding the compiler. For larger projects, however, we often want to use a more -powerful build tool that can manage a project’s dependencies on other libraries.

+

Until now, we have used the builtin Clojure(Script) toolchain to compile our +source files to JavaScript. Now this is a time to understand how manage external +and/or third party dependencies.

+
+

4.4.1. First project

-

For this reason, the remainder of this chapter will explain how to use Leiningen, -the de facto clojure build and dependency management tool, for building -ClojureScript projects. The boot build tool is also growing in popularity, but -for the purposes of this book we will limit ourselves to Leiningen.

+

The best way to show how a tool works is by creating a toy project with it. In +this case, we will create a small application that determines if a year is a +leap year or not.

-
-

4.4.1. Installing leiningen

-

The installation process of leiningen is quite simple; just follow these steps:

+

Let’s start creating the project layout:

-
mkdir ~/bin
-cd ~/bin
-wget https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein
-chmod a+x ./lein
-export PATH=$PATH:~/bin
-
+
mkdir -p leapyears/src/leapyears
+mkdir -p leapyears/target/public
+touch leapyears/target/public/index.html
+touch leapyears/src/leapyears/core.cljs
+touch leapyears/tools.cljs
+touch leapyears/deps.edn
-
-

Make sure that the ~/bin directory is always set on your path. To make it -permanent, add the line starting with export to your ~/.bashrc file (assuming you -are using the bash shell).

-

Now, open another clean terminal and execute lein version. You should see -something like the following:

+

The project has the following structure:

-
$ lein version
-Leiningen 2.5.1 on Java 1.8.0_45 OpenJDK 64-Bit Server VM
+
leapyears
+├── deps.edn
+├── src
+│   └── leapyears
+│       └── core.cljs
+├── target
+│   └── public
+│       └── index.html
+└── tools.clj
+
+
+

The deps.edn file contains information about all the packaged dependencies +needed to build or execute the application. Packaged dependencies are libraries +packaged as jar files and uploaded to clojars/maven repository.

@@ -5281,94 +5531,119 @@

4.4.1
Note

- -
-We assume here that you are using a Unix-like system such as Linux or BSD. If -you are a Windows user, please check the instructions on the -Leiningen homepage. You can also get the Linux/Mac OS -X/BSD version of the leiningen script at the web site. -
+
+

But ClojureScript can consume external code in many different ways:

+
+
    +
  • +

    google closure library module

    +
  • +
  • +

    global export module

    +
  • +
  • +

    es6/commonjs module (experimental)

    +
  • +
-
-

4.4.2. First project

-

The best way to show how a tool works is by creating a toy project with it. In this -case, we will create a small application that determines if a year is a leap year or -not. To start, we will use the mies leiningen template.

+

This will be explained in the following sections.

-
- - - -
-
Note
-
-Templates are a facility in leiningen for creating an initial project -structure. The clojure community has a great many of them. In this case we’ll use -the mies template that was started by the clojurescript core developer. Consult -the leiningen docs to learn more about templates.
-

Let’s start creating the project layout:

+

Let’s start with a simple deps.edn file:

-
$ lein new mies leapyears
-$ cd leapyears # move into newly created project directory
+
{:deps {org.clojure/clojurescript {:mvn/version "1.10.520"}
+        org.clojure/clojure {:mvn/version "1.10.1"}
+        com.bhauman/rebel-readline-cljs {:mvn/version "0.1.4"}
+        com.bhauman/rebel-readline {:mvn/version "0.1.4"}}
+ :paths ["src" "target"]}
-

The project has the following structure:

+

And simple build script (tools.clj file):

-
leapyears
-├── index.html
-├── project.clj
-├── README.md
-├── scripts
-│   ├── build
-│   ├── release
-│   ├── watch
-│   ├── repl
-│   └── brepl
-└── src
-    └── leapyears
-        └── core.cljs
-
+
(require '[cljs.build.api :as b])
+(require '[cljs.repl :as repl])
+(require '[cljs.repl.node :as node])
+
+(defmulti task first)
+
+(defmethod task :default
+  [args]
+  (let [all-tasks  (-> task methods (dissoc :default) keys sort)
+        interposed (->> all-tasks (interpose ", ") (apply str))]
+    (println "Unknown or missing task. Choose one of:" interposed)
+    (System/exit 1)))
+
+(def build-opts
+  {:output-to "target/public/js/leapyears.js"
+   :source-map true
+   :output-dir "target/public/js/leapyears"
+   :main 'leapyears.core
+   :verbose true
+   :optimizations :none})
+
+(defmethod task "repl"
+  [args]
+  (repl/repl (node/repl-env)
+             :output-dir "target/nodejs"
+             :cache-analysis true))
+
+(defmethod task "build"
+  [args]
+  (b/build "src" build-opts))
+
+(defmethod task "watch"
+  [args]
+  (b/watch "src" build-opts))
+
+(task *command-line-args*)
-
-

The project.clj file contains information that Leiningen uses to download -dependencies and build the project. For now, just trust that everything in that file -is exactly as it should be.

-

Open the index.html file and add the following content at the beginning of body:

+

Then, write the following content into target/public/index.html file:

-
<section class="viewport">
-  <div id="result">
-    ----
-  </div>
-  <form action="" method="">
-    <label for="year">Enter a year</label>
-    <input id="year" name="year" />
-  </form>
-</section>
+
<!DOCTYPE html>
+<html>
+  <head>
+    <title>leapyears</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+  </head>
+  <body>
+    <section class="viewport">
+      <div id="result">
+        ----
+      </div>
+
+      <form action="" method="">
+        <label for="year">Input a year</label>
+        <input id="year" name="year" />
+      </form>
+    </section>
+
+    <script src="./js/leapyears.js" type="text/javascript"></script>
+  </body>
+</html>
-

The next step is adding some code to make the form interactive. Put the following -code into the src/leapyears/core.cljs:

+

The next step consist in add some code to make the form interactive. Put the +following code into the src/leapyears/core.cljs:

-
(ns leapyears.core
+
(ns leapyears.core
   (:require [goog.dom :as dom]
             [goog.events :as events]
             [cljs.reader :refer (read-string)]))
@@ -5396,90 +5671,56 @@ 

4.4.2. First projec

-

Now, compile the clojurescript code with:

-
-
-
-
$ ./scripts/watch
-
-
-
-

Behind the scenes, the watch script uses the lein build tool to execute a command -similar to the java build command from the previous sections:

+

Now, we can compile the project with:

-
rlwrap lein trampoline run -m clojure.main scripts/watch.clj
-
+
clojure tools.clj watch
-
- - - - - -
-
Warning
-
-You must have rlwrap installed on your system. -
-

Finally, open the index.html file in a browser. Typing a year in the textbox +

Finally, open the target/public/index.html file in a browser. Typing a year in the textbox should display an indication of its leap year status.

-
-

You may have noticed other files in the scripts directory, like build and -release. These are the same build scripts mentioned in the previous section, but -we will stick with watch here.

-
-

4.4.3. Managing dependencies

+

4.4.2. Adding native dependencies

-

The real purpose of using Leiningen for the ClojureScript compilation process is to -automate the retrieval of dependencies. This is dramatically simpler than retrieving -them manually.

+

Until now we have used only the batteries included in the ClojureScript runtime, +let improve our project including a native dependency. In this example we will +use the Cuerdas (a string manipulation +library build especifically for Clojure(Script)).

-

The dependencies, among other parameters, are declared in the project.clj file and -have this form (from the mies template):

+

Add funcool/cuerdas {:mvn/version "2.2.0"} into the :deps section inside the +deps.edn file. And add the corresponding modifications to the +leapyears/core.cljs file:

-
(defproject leapyears "0.1.0-SNAPSHOT"
-  :description "FIXME: write this!"
-  :url "http://example.com/FIXME"
-  :dependencies [[org.clojure/clojure "1.8.0"]
-                 [org.clojure/clojurescript "1.9.36"]
-                 [org.clojure/data.json "0.2.6"]]
-  :jvm-opts ^:replace ["-Xmx1g" "-server"]
-  :node-dependencies [[source-map-support "0.3.2"]]
-  :plugins [[lein-npm "0.5.0"]]
-  :source-paths ["src" "target/classes"]
-  :clean-targets ["out" "release"]
-  :target-path "target")
+
(ns leapyears.core
+  (:require [goog.dom :as dom]
+            [goog.events :as events]
+            [cuerdas.core :as str]
+            [cljs.reader :refer (read-string)]))
+
+;; [...]
+
+(defn on-change
+  [event]
+  (let [target (.-target event)
+        value (read-string (.-value target))]
+
+    (if (str/blank? value)
+      (set! (.-innerHTML result) "---")
+      (if (leap? value)
+        (set! (.-innerHTML result) "YES")
+        (set! (.-innerHTML result) "NO")))))
-

And here is a brief explanation of the properties relevant for ClojureScript:

-
-
-
    -
  • -

    :dependencies: a vector of dependencies that your project needs.

    -
  • -
  • -

    :clean-targets: a vector of paths that lein clean should delete.

    -
  • -
-
-
-

The dependencies in ClojureScript are packaged using jar files. If you are coming -from Clojure or any JVM language, jar files will be very familiar to you. But if -you aren’t familiar with them, do not worry: a .jar file is like a plain zip file -that contains the project.clj for the library, some metadata, and the ClojureScript -sources. The packaging will be explained in another section.

+

Now, if you run the build or watch command, the new declared dependency will be +downloaded and the application will be compiled with this dependency included.

Clojure packages are often published on Clojars. You can @@ -5487,9 +5728,8 @@

4.4 ClojureScript Wiki.

-
-
-

4.5. External dependencies

+
-

4.5.1. Closure Module compatible library

+

4.4.4. Closure compatible module

-

If you have a library that is just writtent to be compatible with google closure -module system and you want to include it on your project you should just put -it in the source (classpath) and access it like any other clojure namespace.

+

If you have a library that is written to be compatible with google closure +module system and you want to include it on your project: put the source into +the classpath (inside src or vendor directory in leapyears project) and +access it like any other clojure namespace.

-
lein new mies myextmods
-cd myextmods
+
touch src/leapyears/util.js
-

Create a simple google closure module for experiment:

+

And add the implementation using closure module format:

-
src/myextmods/myclosuremodule.js
+
src/leapyears/util.js
-
goog.provide("myextmods.myclosuremodule");
+
goog.provide("leapyears.util");
 
 goog.scope(function() {
-  var module = myextmods.myclosuremodule;
-  module.get_greetings = function() {
-    return "Hello from google closure module.";
+  var module = leapyears.util;
+
+  module.isLeap = function(val) {
+    return (0 === (val % 400)) || (((val % 100) > 0) && (0 === (val % 4)));
   };
 });
-

Now, open the repl, require the namespace and try to use the exposed function:

+

Now, if you open the repl, you can import the namespace and use the isLeap +function

-
(require '[myextmods.myclosuremodule :as cm])
-(cm/get_greetings)
-;; => "Hello from google closure module."
+
(require '[leapyears.util :as util])
+
+(util/isLeap 2112)
+;; => true
+
+(util/isLeap 211)
+;; => false
+
+
+ + + + + +
+
Note
+
+you can open the nodejs repl just executing clj tools.clj repl in the +root of the project. +
@@ -5553,146 +5817,221 @@

Note

-you can open the nodejs repl just executing ./scripts/repl on the -root of the repository. +this is the approach used by many projects to implement some performance +sensitive logic directly in javascript and export it in an easy way to +ClojureScript
-

4.5.2. CommonJS modules compatible libraries

+

4.4.5. Global Export

+
+

This is the most extended and the most reliable way to consume external +javascript libraries from ClojureScript and it has many facilities.

+
+
+

The fastest way to include a javascript library is looking if it is available in +CLJSJS. If it is available, just include the +dependency in the deps.edn file and use it.

+
-

Due to the Node.JS popularity the commonjs used in node is today the most used -module format for javascript libraries, independently if they will be used in server -side development using nodejs or using browser side applications.

+

That libraries has two ways of use it, let’s see some examples.

-

Let’s play with that. Start creating a simple file using commonjs module format -(pretty analgous to the previous example using google closure modules):

+

Start adding moment dependency to deps.edn file:

-
src/myextmods/mycommonjsmodule.js
-
function getGreetings() {
-  return "Hello from commonjs module.";
-}
-
-exports.getGreetings = getGreetings;
+
cljsjs/moment {:mvn/version "2.24.0-0"}
-

Later, in order to use that simple pet library you should indicate to the -ClojureScript compiler the path to that file and the used module type with -:foreign-libs attribute.

+

Then, open the repl and try the following:

+
+
+
using the js/ special namespace
+
+
(require '[cljsjs.moment]) ;; just require, no alias
+
+(.format (js/moment) "LLLL")
+;; => "Monday, July 15, 2019 5:32 PM"
-
-

Open scripts/repl.clj and modify it to somethig like this:

+
using the alias
-
(require
-  '[cljs.repl :as repl]
-  '[cljs.repl.node :as node])
+
(require '[moment :as m])
 
-(repl/repl
- (node/repl-env)
- :language-in  :ecmascript5
- :language-out :ecmascript5
- :foreign-libs [{:file "myextmods/mycommonjsmodule.js"
-                 :provides ["myextmods.mycommonjsmodule"]
-                 :module-type :commonjs}]
- :output-dir "out"
- :cache-analysis false)
+(.format (m) "LLLL") +;; => "Monday, July 15, 2019 5:33 PM"
-
- - - - - -
-
Note
-
-Although the direct path is used to point to this pet library you can specify -a full URI to remote resource and it will be automatically downloaded. -
+
+

Behind the scenes that packages uses the ClojureScript compiler facilities +descibed +here for provide the compiler with enough information about the files and +global exports to use.

-

Now, let’s try to play with moment within the repl (executing the ./scripts/repl -script that uses the previously modified ./scripts/repl.clj file):

+

So, if don’t find a library in cljsjs, we can include it using the same +facilities. Let’s assume that moment is not available on cljsjs and we need it +on our project.

+
+
+

For include an foreign dependency we need to pass :foreign-libs and :externs +params to the ClojureScript compiler, and we have two ways:

+
+
+
    +
  • +

    passing them to the build or repl functions.

    +
  • +
  • +

    inside the deps.cljs file located on the root of the classpath.

    +
  • +
+
+
+

The deps.cljs approach requires that files should be localted on the local +directories, but the first approach allows specify directly external urls. We +will use the first approach on our example.

+
+
+

This is how looks the deps.edn file with the changes applied:

-
(require '[myextmods.mycommonjsmodule :as cm])
-(cm/getGreetings)
-;; => "Hello from commonjs module."
+
;; [...]
+
+(def foreign-libs
+  [{:file "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.js"
+    :file-min "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"
+    :provides ["vendor.moment"]}])
+
+
+(def build-opts
+  {:output-to "target/public/js/leapyears.js"
+   :source-map true
+   :output-dir "target/public/js/leapyears"
+   :main 'leapyears.core
+   :verbose true
+   :optimizations :none
+   :foreign-libs foreign-libs})
+
+(defmethod task "repl"
+  [args]
+  (repl/repl (node/repl-env)
+             :foreign-libs foreign-libs
+             :output-dir "target/node"
+             :cache-analysis true))
+
+;; [...]
+
+
+
+

Now, if you excute the repl, you will be able to import vendor.moment in the same way +if you are using the cljsjs package.

+
+

Finally, there are the :externs option, that will be needed only for the +production build, and the externs file consists in plain javascript files that +declares the public API of the included foreign libraries and make them +understandable to the google closure compiler.

+
+
+

The moment externs are available +here +and if you include some library that you want to use, and then want to compile +your app with advanced optimizations you will need to include a file similar to +the moment.ext.js on the project and referenciate it with :externs option to +the ClojureScript compiler.

+
+
+

More info on clojurescript.org.

-

4.5.3. Legacy, module-less (global scope) libraries

+

4.4.6. ES6/CommonJS modules

-

Although today is very common have libraries packaged using some kind of modules, -there are also a great amount of libraries that just exposes a global objects and -does not uses any kind of modules; and you may want to use them from -ClojureScript.

+

Google Closure Compiler has an advanced feature that allows convert from +different module types (commonjs and ES) into google closure module +type. Although this feature is still experimental. With simple modules it works, +but with more complex modules (many submodules and directories) still doest +not complies correctly.

-

In order to use a library that exposes a global object, you should follow similar -steps as with commojs modules with the exception that you should omit the -:module-type attribute.

+

In any way I invite you to experiment with it. You can found more documentation +on +clojurescript.org.

-

This will create a synthetic namespace that you should require in order to be able -to access to the global object through the js/ namespace. The namespace is called -synthetic because it does not expose any object behind it, it just indicates -to the compiler that you want that dependency.

+

The best way to use ES6 and/or CommonJS module is combining a javascript bundler +like rollup or webpack to generate a single build with external dependencies +and thn use the global exports method to use it in ClojureScript. An example +of this is explained on clojurescript.org.

+
+
+
+

4.5. Interactive development with Figwheel

-

Let’s play with that. Start creating a simple file declaring just a global function:

+

And finally we will introduce figwheel, that enables fully interactive, +REPL-based and hot reloading enabled development environment.

-
-
src/myextmods/myglobalmodule.js
-
-
function getGreetings() {
-  return "Hello from global scope.";
-}
+
+

We will reuse the leapyears project structure for the following examples.

+
+ + + + + +
+
Note
+
+Although we use figwheel here for web application, it works in the same +way on the application that targets nodejs as execution environment. +
-

Open scripts/repl.clj and modify it to somethig like this:

+

As first step, we need to add figwheel dependency to the deps.edn file:

-
(require
-  '[cljs.repl :as repl]
-  '[cljs.repl.node :as node])
-
-(repl/repl
- (node/repl-env)
- :language-in  :ecmascript5
- :language-out :ecmascript5
- :foreign-libs [{:file "myextmods/mycommonjsmodule.js"
-                 :provides ["myextmods.mycommonjsmodule"]
-                 :module-type :commonjs}
-                {:file "myextmods/myglobalmodule.js"
-                 :provides ["myextmods.myglobalmodule"]}]
- :output-dir "out"
- :cache-analysis false)
+
com.bhauman/figwheel-main {:mvn/version "0.2.1"}
-

And in the same way as in previous examples, let evaluate that in the repl:

+

Then, add new task to the tools.clj script:

-
(require 'myextmods.myglobalmodule)
-(js/getGreetings)
-;; => "Hello from global scope."
+
(def figwheel-opts
+  {:open-url false
+   :load-warninged-code true
+   :auto-testing false
+   :ring-server-options {:port 3448 :host "0.0.0.0"}
+   :watch-dirs ["src"]})
+
+(defmethod task "figwheel"
+  [args]
+  (figwheel/start figwheel-opts {:id "main" :options build-opts}))
+
+
+
+

And then, run clojure tools.clj fighweel. This will start the figwheel process +that automatically will launch a http server that will serve the +target/public/ directory and index to index.html file.

+
+

If you update the code, that code will be automatically loaded to the browser, +without page reload.

+
+

For more info: figwheel.org.

@@ -5725,54 +6064,61 @@

4.6.1. First steps< them here.

-

Start creating a new project using the mies leiningen template for experimenting -with tests:

+

We will reuse the leapyears project structure and we will add testing to it. Let’s create +the test related files and directories:

-
$ lein new mies mytestingapp
-$ cd mytestingapp
-
+
mkdir -p test/leapyears/test
+touch test/leapyears/test/main.cljs
-
-

This project will contain the same layout as we have seen in the dependency -management subchapter, so we won’t explain it again.

-

The next step is a creating a directory tree for our tests:

+

Also we will need to create new tasks on our tools.clj file for build, watch and run +tests:

-
$ mkdir -p test/mytestingapp
-$ touch test/mytestingapp/core_tests.cljs
-
-
-
-

Also, we should adapt the existing watch.clj script to work with this newly -created test directory:

-
-
-
-
(require '[cljs.build.api :as b])
+
(require '[clojure.java.shell :as shell])
 
-(b/watch (b/inputs "test" "src")
-  {:main 'mytestingapp.core_tests
-   :target :nodejs
-   :output-to "out/mytestingapp.js"
-   :output-dir "out"
-   :verbose true})
-
+;; [...] + +(defmethod task "build:tests" + [args] + (b/build (b/inputs "src" "vendor" "test") + (assoc build-opts + :main 'leapyears.test.main + :output-to "out/tests.js" + :output-dir "out/tests" + :target :nodejs))) + +(defmethod task "watch:test" + [args] + (letfn [(run-tests [] + (let [{:keys [out err]} (shell/sh "node" "out/tests.js")] + (println out err)))] + (println "Start watch loop...") + (try + (b/watch (b/inputs "src", "test") + (assoc build-opts + :main 'leapyears.test.main + :watch-fn run-tests + :output-to "out/tests.js" + :output-dir "out/tests" + :target :nodejs)) + + (catch Exception e + (println "Error on running tests:" e) + ;; (Thread/sleep 2000) + (task args)))))

-
-

This new script will compile and watch both directories "src" and "test", and it -sets the new entry point to the mytestingapp.core_tests namespace.

-

Next, put some test code in the core_tests.cljs file:

+

Next, put some test code in the test/leapyears/test/main.cljs file:

-
(ns mytestingapp.core-tests
+
(ns leapyears.test.main
   (:require [cljs.test :as t]))
 
 (enable-console-print!)
@@ -5780,7 +6126,15 @@ 

4.6.1. First steps< (t/deftest my-first-test (t/is (= 1 2))) -(set! *main-cli-fn* #(t/run-tests))

+(set! *main-cli-fn* #(t/run-tests)) + +;; This extension is required for correctly set the return code +;; depending if the test passes or not. +(defmethod t/report [:cljs.test/default :end-run-tests] + [m] + (if (t/successful? m) + (set! (.-exitCode js/process) 0) + (set! (.-exitCode js/process) 1)))
@@ -5788,7 +6142,7 @@

4.6.1. First steps<

-
(t/deftest my-first-test
+
(t/deftest my-first-test
   (t/is (= 1 2)))
@@ -5798,26 +6152,12 @@

4.6.1. First steps< this example, we try to assert that (= 1 2) is true.

-

Let’s try to run this. First start the watch process:

-
-
-
-
$ ./scripts/watch
-Building ...
-Copying jar:file:/home/niwi/.m2/repository/org/clojure/clojurescript/1.9.36/clojurescript-1.9.36.jar!/cljs/core.cljs to out/cljs/core.cljs
-Reading analysis cache for jar:file:/home/niwi/.m2/repository/org/clojure/clojurescript/1.9.36/clojurescript-1.9.36.jar!/cljs/core.cljs
-Compiling out/cljs/core.cljs
-... done. Elapsed 3.862126827 seconds
-Watching paths: /home/niwi/cljsbook/playground/mytestingapp/test, /home/niwi/cljsbook/playground/mytestingapp/src
-
-
-
-

When the compilation is finished, try to run the compiled file with nodejs:

+

Let’s try to run this:

-
$ node out/mytestingapp.js
-
+
$ clojure tools build:tests
+$ node out/tests.js
 Testing mytestingapp.core-tests
 
 FAIL in (my-first-test) (cljs/test.js:374:14)
@@ -5834,7 +6174,8 @@ 

4.6.1. First steps<

-
$ node out/mytestingapp.js
+
$ clojure tools build:tests
+$ node out/mytestingapp.js
 
 Testing mytestingapp.core-tests
 
@@ -5843,40 +6184,23 @@ 

4.6.1. First steps<

-

It is fine to test these kinds of assertions, but they are not very useful. Let’s go -to test some application code. For this, we will use a function to check if a year -is a leap year or not. Write the following content to the -src/mytestingapp/core.clj file:

+

It is fine to test these kinds of assertions, but they are not very +useful. Let’s go to test some application code. For this, we will use a function +to check if a year is a leap year or not:

-
(defn leap?
-  [year]
-  (and (zero? (js-mod year 4))
-       (pos? (js-mod year 100))
-       (pos? (js-mod year 400))))
-
-
-
-

Next, write a new test case to check that our new leap? function works -properly. Make the core_tests.cljs file look like:

-
-
-
-
(ns mytestingapp.core-tests
+
(ns leapyears.test.main
   (:require [cljs.test :as t]
-            [mytestingapp.core :as core]))
+            [leapyears.vendor.util-closure :as util]))
 
-(enable-console-print!)
-
-(t/deftest my-first-test
-  (t/is (not= 1 2)))
+;; [...]
 
 (t/deftest my-second-test
-  (t/is (core/leap? 1980))
-  (t/is (not (core/leap? 1981))))
+  (t/is (util/isLeap 1980))
+  (t/is (not (util/isLeap 1981))))
 
-(set! *main-cli-fn* #(t/run-tests))
+;; [...]
@@ -5902,14 +6226,9 @@

4.6.2. Async Testin

-
(defn async-leap?
+
(defn async-leap?
   [year callback]
-  (js/setImmediate
-   (fn []
-     (let [result (or (zero? (js-mod year 400))
-                      (and (pos? (js-mod year 100))
-                           (zero? (js-mod year 4))))]
-       (callback result)))))
+ (js/setImmediate #(callback (util/isLeap year))))
-
(t/deftest my-async-test
+
(t/deftest my-async-test
   (t/async done
-    (core/async-leap? 1980 (fn [result]
-                             (t/is (true? result))
-                             (done)))))
+ (async-leap? 1980 (fn [result] + (t/is (true? result)) + (done)))))
- -
-

4.6.4. Integrating with CI

-
-

Most continuous integration tools and services expect that test scripts you provide -return a standard exit code. But the ClojureScript test framework cannot customize -this exit code without some configuration, because JavaScript lacks a universal exit -code API for ClojureScript to use.

-
-
-

To fix this, the ClojureScript test framework provides an avenue for executing -custom code after the tests are done. This is where you are expected to set the -environment-specific exit code depending on the final test status: 0 for success, -1 for failure.

-
-
-

Insert this code at the end of core_tests.cljs:

-
-
-
-
(defmethod t/report [::t/default :end-run-tests]
-  [m]
-  (if (t/successful? m)
-    (set! (.-exitCode js/process) 0)
-    (set! (.-exitCode js/process) 1)))
-
-
-
-

Now, you may check the exit code of the test script after running:

-
-
-
-
$ node out/mytestingapp.js
-$ echo $?
-
-
-
-

This code snippet obviously assumes that you are running the tests using nodejs. -If you are running your script in another execution environment, you should be aware -of how you can set the exit code in that environment and modify the previous snippet -accordingly.

-
-

@@ -6011,7 +6282,7 @@

5.1.1.

-
(def process-clusters
+
(def process-clusters
   (comp
     (partial map clean-grape)
     (partial filter not-rotten)
@@ -6090,7 +6361,7 @@ 

-
(defn my-map
+
(defn my-map
   [f coll]
   (when-let [s (seq coll)]
     (cons (f (first s)) (my-map f (rest s)))))
@@ -6120,7 +6391,7 @@ 

-
(defn my-mapr
+
(defn my-mapr
   [f coll]
   (reduce (fn [acc input]         ;; reducing function
             (conj acc (f input)))
@@ -6163,7 +6434,7 @@ 

-

First of all, my-mapt takes a mapping function; in the example we are giving it -inc and getting another function back. Let’s replace f with inc to see what we -are building:

+

First of all, my-mapt takes a mapping function; in the example we are giving +it inc and getting another function back. Let’s replace f with inc to see +what we are building:

To ilustrate the generality of transducers, let’s use different sources and @@ -6231,7 +6502,7 @@

-

Now that we know more about transducers we can try to implement our own version of -mapcat. We already have a fundamental piece of it: the map transducer. What -mapcat does is map a function over an input and flatten the resulting structure one -level. Let’s try to implement the catenation part as a transducer:

+

Now that we know more about transducers we can try to implement our own version +of mapcat. We already have a fundamental piece of it: the map +transducer. What mapcat does is map a function over an input and flatten the +resulting structure one level. Let’s try to implement the catenation part as a +transducer:

-
(defn my-mapcat
+
(defn my-mapcat
   [f]
   (comp (my-mapt f) my-cat))
 
@@ -6288,13 +6561,13 @@ 

5.1.3. Transducers in ClojureScript core

-

Some of the ClojureScript core functions like map, filter and mapcat support an -arity 1 version that returns a transducer. Let’s revisit our definition of -process-cluster and define it in terms of transducers:

+

Some of the ClojureScript core functions like map, filter and mapcat +support an arity 1 version that returns a transducer. Let’s revisit our +definition of process-cluster and define it in terms of transducers:

-

Also you may have noticed that the order in which they are composed is reversed, they -appear in the order they are executed. Note that all map, filter and mapcat -return a transducer. filter transforms the reducing function returned by map, -applying the filtering before proceeding; mapcat transforms the reducing function -returned by filter, applying the mapping and catenation before proceeding.

+

Also you may have noticed that the order in which they are composed is reversed, +they appear in the order they are executed. Note that all map, filter and +mapcat return a transducer. filter transforms the reducing function returned +by map, applying the filtering before proceeding; mapcat transforms the +reducing function returned by filter, applying the mapping and catenation +before proceeding.

-

One of the powerful properties of transducers is that they are combined using regular -function composition. What’s even more elegant is that the composition of various -transducers is itself a transducer! This means that our process-cluster is a -transducer too, so we have defined a composable and context-independent algorithmic -transformation.

+

One of the powerful properties of transducers is that they are combined using +regular function composition. What’s even more elegant is that the composition +of various transducers is itself a transducer! This means that our +process-cluster is a transducer too, so we have defined a composable and +context-independent algorithmic transformation.

-
(conj)
+
(conj)
 ;; => []
-

The conj function has a arity 0 version that returns an empty vector but is not the -only reducing function that supports arity 0. Let’s explore some others:

+

The conj function has a arity 0 version that returns an empty vector but is +not the only reducing function that supports arity 0. Let’s explore some others:

-

The reducing function returned by transducers must support the arity 0 as well, which -will typically delegate to the transformed reducing function. There is no sensible -implementation of the arity 0 for the transducers we have implemented so far, so -we’ll simply call the reducing function without arguments. Here’s how our modified -my-mapt could look like:

+

The reducing function returned by transducers must support the arity 0 as well, +which will typically delegate to the transformed reducing function. There is no +sensible implementation of the arity 0 for the transducers we have implemented +so far, so we’ll simply call the reducing function without arguments. Here’s how +our modified my-mapt could look like:

-

The call to the arity 0 of the reducing function returned by a transducer will call -the arity 0 version of every nested reducing function, eventually calling the -outermost reducing function. Let’s see an example with our already defined +

The call to the arity 0 of the reducing function returned by a transducer will +call the arity 0 version of every nested reducing function, eventually calling +the outermost reducing function. Let’s see an example with our already defined process-clusters transducer:

-
((process-clusters conj))
+
((process-clusters conj))
 ;; => []
@@ -6428,27 +6702,28 @@

5.1.4. Initialisa

5.1.5. Stateful transducers

-

So far we’ve only seen purely functional transducer; they don’t have any implicit -state and are very predictable. However, there are many data transformation functions -that are inherently stateful, like take. take receives a number n of elements -to keep and a collection and returns a collection with at most n elements.

+

So far we’ve only seen purely functional transducer; they don’t have any +implicit state and are very predictable. However, there are many data +transformation functions that are inherently stateful, like take. take +receives a number n of elements to keep and a collection and returns a +collection with at most n elements.

-
(take 10 (range 100))
+
(take 10 (range 100))
 ;; => (0 1 2 3 4 5 6 7 8 9)

Let’s step back for a bit and learn about the early termination of the reduce -function. We can wrap an accumulator in a type called reduced for telling reduce -that the reduction process should terminate immediately. Let’s see an example of a -reduction that aggregates the inputs in a collection and finishes as soon as there -are 10 elements in the accumulator:

+function. We can wrap an accumulator in a type called reduced for telling +reduce that the reduction process should terminate immediately. Let’s see an +example of a reduction that aggregates the inputs in a collection and finishes +as soon as there are 10 elements in the accumulator:

-
(reduce (fn [acc input]
+
(reduce (fn [acc input]
           (if (= (count acc) 10)
             (reduced acc)
             (conj acc input)))
@@ -6458,20 +6733,21 @@ 

5.1.5

-

Since transducers are modifications of reducing functions they also use reduced for -early termination. Note that stateful transducers may need to do some cleanup before -the process terminates, so they must support an arity 1 as a "completion" -step. Usually, like with arity 0, this arity will simply delegate to the transformed -reducing function’s arity 1.

+

Since transducers are modifications of reducing functions they also use +reduced for early termination. Note that stateful transducers may need to do +some cleanup before the process terminates, so they must support an arity 1 as a +"completion" step. Usually, like with arity 0, this arity will simply delegate +to the transformed reducing function’s arity 1.

-

Knowing this we are able to write stateful transducers like take. We’ll be using -mutable state internally for tracking the number of inputs we have seen so far, and -wrap the accumulator in a reduced as soon as we’ve seen enough elements:

+

Knowing this we are able to write stateful transducers like take. We’ll be +using mutable state internally for tracking the number of inputs we have seen so +far, and wrap the accumulator in a reduced as soon as we’ve seen enough +elements:

-
(defn my-take
+
(defn my-take
   [n]
   (fn [rfn]
     (let [remaining (volatile! n)]
@@ -6497,14 +6773,14 @@ 

5.1.5

The first thing to notice is that we are creating a mutable value inside the transducer. Note that we don’t create it until we receive a reducing function to -transform. If we created it before returning the transducer we couldn’t use my-take -more than once. Since the transducer is handed a reducing function to transform each -time it is used, we can use it multiple times and the mutable variable will be -created in every use.

+transform. If we created it before returning the transducer we couldn’t use +my-take more than once. Since the transducer is handed a reducing function to +transform each time it is used, we can use it multiple times and the mutable +variable will be created in every use.

-

Let’s now dig into the reducing function returned from my-take. First of all we -deref the volatile to get the number of elements that remain to be taken and -decrement it to get the next remaining value. If there are still remaining items to -take, we call rfn passing the accumulator and input; if not, we already have the -final result.

+

Let’s now dig into the reducing function returned from my-take. First of all +we deref the volatile to get the number of elements that remain to be taken +and decrement it to get the next remaining value. If there are still remaining +items to take, we call rfn passing the accumulator and input; if not, we +already have the final result.

The body of my-take should be obvious by now. We check whether there are still -items to be processed using the next remainder (nr) and, if not, wrap the result in -a reduced using the ensure-reduced function. ensure-reduced will wrap the value -in a reduced if it’s not reduced already or simply return the value if it’s already -reduced. In case we are not done yet, we return the accumulated result for further -processing.

+items to be processed using the next remainder (nr) and, if not, wrap the +result in a reduced using the ensure-reduced function. ensure-reduced will +wrap the value in a reduced if it’s not reduced already or simply return the +value if it’s already reduced. In case we are not done yet, we return the +accumulated result for further processing.

-
(if (not (pos? nr))
+
(if (not (pos? nr))
   (ensure-reduced result)
   result)

We’ve seen an example of a stateful transducer but it didn’t do anything in its -completion step. Let’s see an example of a transducer that uses the completion step -to flush an accumulated value. We’ll implement a simplified version of -partition-all, which given a n number of elements converts the inputs in vectors -of size n. For understanding its purpose better let’s see what the arity 2 version -gives us when providing a number and a collection:

+completion step. Let’s see an example of a transducer that uses the completion +step to flush an accumulated value. We’ll implement a simplified version of +partition-all, which given a n number of elements converts the inputs in +vectors of size n. For understanding its purpose better let’s see what the +arity 2 version gives us when providing a number and a collection:

-
(partition-all 3 (range 10))
+
(partition-all 3 (range 10))
 ;; => ((0 1 2) (3 4 5) (6 7 8) (9))

The transducer returning function of partition-all will take a number n and -return a transducer that groups inputs in vectors of size n. In the completion step -it will check if there is an accumulated result and, if so, add it to the -result. Here’s a simplified version of ClojureScript core partition-all function, -where array-list is a wrapper for a mutable JavaScript array:

+return a transducer that groups inputs in vectors of size n. In the completion +step it will check if there is an accumulated result and, if so, add it to the +result. Here’s a simplified version of ClojureScript core partition-all +function, where array-list is a wrapper for a mutable JavaScript array:

-
(defn my-partition-all
+
(defn my-partition-all
   [n]
   (fn [rfn]
     (let [a (array-list)]
@@ -6607,16 +6883,16 @@ 

5.1.5

5.1.6. Eductions

-

Eductions are a way to combine a collection and one or more transformations that can -be reduced and iterated over, and that apply the transformations every time we do -so. If we have a collection that we want to process and a transformation over it that -we want others to extend, we can hand them a eduction, encapsulating the source -collection and our transformation. We can create an eduction with the eduction -function:

+

Eductions are a way to combine a collection and one or more transformations that +can be reduced and iterated over, and that apply the transformations every time +we do so. If we have a collection that we want to process and a transformation +over it that we want others to extend, we can hand them a eduction, +encapsulating the source collection and our transformation. We can create an +eduction with the eduction function:

-
(def ed (eduction (filter odd?) (take 5) (range 100)))
+
(def ed (eduction (filter odd?) (take 5) (range 100)))
 
 (reduce + 0 ed)
 ;; => 25
@@ -6655,13 +6931,13 @@ 

5.1.8. Defining our own transducers

-

There a few things to consider before writing our own transducers so in this section -we will learn how to properly implement one. First of all, we’ve learned that the -general structure of a transducer is the following:

+

There a few things to consider before writing our own transducers so in this +section we will learn how to properly implement one. First of all, we’ve learned +that the general structure of a transducer is the following:

-
(fn [xf]
+
(fn [xf]
   (fn
     ([]          ;; init
       ...)
@@ -6694,23 +6970,24 @@ 

5.1.9. Transducible processes

-

A transducible process is any process that is defined in terms of a succession of -steps ingesting input values. The source of input varies from one process to -another. Most of our examples dealt with inputs from a collection or a lazy sequence, -but it could be an asynchronous stream of values or a core.async channel. The -outputs produced by each step are also different for every process; into creates a -collection with every output of the transducer, sequence yields a lazy sequence, -and asynchronous streams would probably push the outputs to their listeners.

+

A transducible process is any process that is defined in terms of a succession +of steps ingesting input values. The source of input varies from one process to +another. Most of our examples dealt with inputs from a collection or a lazy +sequence, but it could be an asynchronous stream of values or a core.async +channel. The outputs produced by each step are also different for every process; +into creates a collection with every output of the transducer, sequence +yields a lazy sequence, and asynchronous streams would probably push the outputs +to their listeners.

In order to improve our understanding of transducible processes, we’re going to -implement an unbounded queue, since adding values to a queue can be thought in terms -of a succession of steps ingesting input. First of all we’ll define a protocol and a -data type that implements the unbounded queue:

+implement an unbounded queue, since adding values to a queue can be thought in +terms of a succession of steps ingesting input. First of all we’ll define a +protocol and a data type that implements the unbounded queue:

-
(defprotocol Queue
+
(defprotocol Queue
   (put! [q item] "put an item into the queue")
   (take! [q] "take an item from the queue")
   (shutdown! [q] "stop accepting puts in the queue"))
@@ -6729,14 +7006,14 @@ 

5

-

We defined the Queue protocol and as you may have noticed the implementation of -UnboundedQueue doesn’t know about transducers at all. It has a put! operation as -its step function and we’re going to implement the transducible process on top of -that interface:

+

We defined the Queue protocol and as you may have noticed the implementation +of UnboundedQueue doesn’t know about transducers at all. It has a put! +operation as its step function and we’re going to implement the transducible +process on top of that interface:

-

As you can see, the unbounded-queue constructor uses an UnboundedQueue instance -internally, proxying the take! and shutdown! calls and implementing the -transducible process logic in the put! function. Let’s deconstruct it to understand -what’s going on.

+

As you can see, the unbounded-queue constructor uses an UnboundedQueue +instance internally, proxying the take! and shutdown! calls and implementing +the transducible process logic in the put! function. Let’s deconstruct it to +understand what’s going on.

-

First of all, we use completing for adding the arity 0 and arity 1 to the Queue -protocol’s put! function. This will make it play nicely with transducers in case -we give this reducing function to xform to derive another. After that, if a -transducer (xform) was provided, we derive a reducing function applying the -transducer to put!. If we’re not handed a transducer we will just use put!. q -is the internal instance of UnboundedQueue.

+

First of all, we use completing for adding the arity 0 and arity 1 to the +Queue protocol’s put! function. This will make it play nicely with +transducers in case we give this reducing function to xform to derive another. +After that, if a transducer (xform) was provided, we derive a reducing +function applying the transducer to put!. If we’re not handed a transducer we +will just use put!. q is the internal instance of UnboundedQueue.

-

The exposed put! operation will only be performed if the queue hasn’t been shut -down. Notice that the put! implementation of UnboundedQueue uses an assert to -verify that we can still put values to it and we don’t want to break that -invariant. If the queue isn’t closed we can put values into it, we use the possibly -transformed xput! for doing so.

+

The exposed put! operation will only be performed if the queue hasn’t been +shut down. Notice that the put! implementation of UnboundedQueue uses an +assert to verify that we can still put values to it and we don’t want to break +that invariant. If the queue isn’t closed we can put values into it, we use the +possibly transformed xput! for doing so.

If the put operation gives back a reduced value it’s telling us that we should -terminate the transducible process. In this case that means shutting down the queue -to not accept more values. If we didn’t get a reduced value we can happily continue -accepting puts.

+terminate the transducible process. In this case that means shutting down the +queue to not accept more values. If we didn’t get a reduced value we can happily +continue accepting puts.

Let’s see how our queue behaves without transducers:

To check that we’ve implemented the transducible process, let’s use a stateful -transducer. We’ll use a transducer that will accept values while they aren’t equal to -4 and will partition inputs in chunks of 2 elements:

+transducer. We’ll use a transducer that will accept values while they aren’t +equal to 4 and will partition inputs in chunks of 2 elements:

Transducible processes must respect reduced as a way for signaling early -termination. For example, building a collection stops when encountering a reduced -and core.async channels with transducers are closed. The reduced value must be -unwrapped with deref and passed to the completion step, which must be called -exactly once.

+termination. For example, building a collection stops when encountering a +reduced and core.async channels with transducers are closed. The reduced +value must be unwrapped with deref and passed to the completion step, which +must be called exactly once.

Transducible processes shouldn’t expose the reducing function they generate when @@ -6905,13 +7182,14 @@

5

5.2. Transients

Although ClojureScript’s immutable and persistent data structures are reasonably -performant there are situations in which we are transforming large data structures -using multiple steps to only share the final result. For example, the core into -function takes a collection and eagerly populates it with the contents of a sequence:

+performant there are situations in which we are transforming large data +structures using multiple steps to only share the final result. For example, the +core into function takes a collection and eagerly populates it with the +contents of a sequence:

-
(into [] (range 100))
+
(into [] (range 100))
 ;; => [0 1 2 ... 98 99]
@@ -6930,7 +7208,7 @@

5.2. Transients

-
(def tv (transient [1 2 3]))
+
(def tv (transient [1 2 3]))
 ;; => #<[object Object]>
@@ -6939,7 +7217,7 @@

5.2. Transients

-
(def tv (transient [1 2 3]))
+
(def tv (transient [1 2 3]))
 
 (nth tv 0)
 ;; => 1
@@ -6962,14 +7240,14 @@ 

5.2. Transients

-

Since transients don’t have persistent and immutable semantics for updates they can’t -be transformed using the already familiar conj or assoc functions. Instead, the -transforming functions that work on transients end with a bang. Let’s look at an -example using conj! on a transient:

+

Since transients don’t have persistent and immutable semantics for updates they +can’t be transformed using the already familiar conj or assoc +functions. Instead, the transforming functions that work on transients end with +a bang. Let’s look at an example using conj! on a transient:

-
(def tv (transient [1 2 3]))
+
(def tv (transient [1 2 3]))
 
 (conj! tv 4)
 ;; => #<[object Object]>
@@ -6980,14 +7258,14 @@ 

5.2. Transients

As you can see, the transient version of the vector is neither immutable nor -persistent. Instead, the vector is mutated in place. Although we could transform tv -repeatedly using conj! on it we shouldn’t abandon the idioms used with the -persistent data structures: when transforming a transient, use the returned version -of it for further modifications like in the following example:

+persistent. Instead, the vector is mutated in place. Although we could transform +tv repeatedly using conj! on it we shouldn’t abandon the idioms used with +the persistent data structures: when transforming a transient, use the returned +version of it for further modifications like in the following example:

-
(-> [1 2 3]
+
(-> [1 2 3]
   transient
   (conj! 4)
   (conj! 5))
@@ -7001,7 +7279,7 @@ 

5.2. Transients

-
(-> [1 2 3]
+
(-> [1 2 3]
   transient
   (conj! 4)
   (conj! 5)
@@ -7011,14 +7289,14 @@ 

5.2. Transients

A peculiarity of transforming transients into persistent structures is that the -transient version is invalidated after being converted to a persistent data structure -and we can’t do further transformations to it. This happens because the derived -persistent data structure uses the transient’s internal nodes and mutating them would -break the immutability and persistent guarantees:

+transient version is invalidated after being converted to a persistent data +structure and we can’t do further transformations to it. This happens because +the derived persistent data structure uses the transient’s internal nodes and +mutating them would break the immutability and persistent guarantees:

-
(def tm (transient {}))
+
(def tm (transient {}))
 ;; => #<[object Object]>
 
 (assoc! tm :foo :bar)
@@ -7033,13 +7311,13 @@ 

5.2. Transients

Going back to our initial example with into, here’s a very simplified -implementation of it that uses a transient for performance, returning a persistent -data structure and thus exposing a purely functional interface although it uses -mutation internally:

+implementation of it that uses a transient for performance, returning a +persistent data structure and thus exposing a purely functional interface +although it uses mutation internally:

-
(defn my-into
+
(defn my-into
   [to from]
   (persistent! (reduce conj! (transient to) from)))
 
@@ -7051,27 +7329,29 @@ 

5.2. Transients

5.3. Metadata

-

ClojureScript symbols, vars and persistent collections support attaching metadata to -them. Metadata is a map with information about the entity it’s attached to. The -ClojureScript compiler uses metadata for several purposes such as type hints, and the -metadata system can be used by tooling, library and application developers too.

+

ClojureScript symbols, vars and persistent collections support attaching +metadata to them. Metadata is a map with information about the entity it’s +attached to. The ClojureScript compiler uses metadata for several purposes such +as type hints, and the metadata system can be used by tooling, library and +application developers too.

-

There may not be many cases in day-to-day ClojureScript programming where you need -metadata, but it is a nice language feature to have and know about; it may come in -handy at some point. It makes things like runtime code introspection and +

There may not be many cases in day-to-day ClojureScript programming where you +need metadata, but it is a nice language feature to have and know about; it may +come in handy at some point. It makes things like runtime code introspection and documentation generation very easy. You’ll see why throughout this section.

5.3.1. Vars

-

Let’s define a var and see what metadata is attached to it by default. Note that this -code is executed in a REPL, and thus the metadata of a var defined in a source file -may vary. We’ll use the meta function to retrieve the metadata of the given value:

+

Let’s define a var and see what metadata is attached to it by default. Note that +this code is executed in a REPL, and thus the metadata of a var defined in a +source file may vary. We’ll use the meta function to retrieve the metadata of +the given value:

-
(def answer-to-everything 42)
+
(def answer-to-everything 42)
 ;; => 42
 
 #'answer-to-everything
@@ -7092,19 +7372,20 @@ 

5.3.1. Vars

-

Few things to note here. First of all, #'answer-to-everything gives us a reference -to the Var that holds the value of the answer-to-everything symbol. We see that -it includes information about the namespace (:ns) in which it was defined, its -name, file (although, since it was defined at a REPL doesn’t have a source file), -source, position in the file where it was defined, argument list (which only makes -sense for functions), documentation string and test function.

+

Few things to note here. First of all, #'answer-to-everything gives us a +reference to the Var that holds the value of the answer-to-everything +symbol. We see that it includes information about the namespace (:ns) in which +it was defined, its name, file (although, since it was defined at a REPL doesn’t +have a source file), source, position in the file where it was defined, argument +list (which only makes sense for functions), documentation string and test +function.

Let’s take a look at a function var’s metadata:

-
(defn add
+
(defn add
   "A function that adds two numbers."
   [x y]
   (+ x y))
@@ -7125,12 +7406,12 @@ 

5.3.1. Vars

We see that the argument lists are stored in the :arglists field of the var’s -metadata and its documentation in the :doc field. We’ll now define a test function -to learn about what :test is used for:

+metadata and its documentation in the :doc field. We’ll now define a test +function to learn about what :test is used for:

-
(require '[cljs.test :as t])
+
(require '[cljs.test :as t])
 
 (t/deftest i-pass
   (t/is true))
@@ -7150,23 +7431,23 @@ 

5.3.1. Vars

-

The :test attribute (truncated for brevity) in the i-pass var’s metadata is a -test function. This is used by the cljs.test library for discovering and running -tests in the namespaces you tell it to.

+

The :test attribute (truncated for brevity) in the i-pass var’s metadata is +a test function. This is used by the cljs.test library for discovering and +running tests in the namespaces you tell it to.

5.3.2. Values

-

We learned that vars can have metadata and what kind of metadata is added to them for -consumption by the compiler and the cljs.test testing library. Persistent -collections can have metadata too, although they don’t have any by default. We can -use the with-meta function to derive an object with the same value and type with -the given metadata attached. Let’s see how:

+

We learned that vars can have metadata and what kind of metadata is added to +them for consumption by the compiler and the cljs.test testing +library. Persistent collections can have metadata too, although they don’t have +any by default. We can use the with-meta function to derive an object with the +same value and type with the given metadata attached. Let’s see how:

-
(def map-without-metadata {:language "ClojureScript"})
+
(def map-without-metadata {:language "ClojureScript"})
 ;; => {:language "ClojureScript"}
 
 (meta map-without-metadata)
@@ -7189,18 +7470,18 @@ 

5.3.2. Values

-

It shouldn’t come as a surprise that metadata doesn’t affect equality between two -data structures since equality in ClojureScript is based on value. Another -interesting thing is that with-meta creates another object of the same type and -value as the given one and attaches the given metadata to it.

+

It shouldn’t come as a surprise that metadata doesn’t affect equality between +two data structures since equality in ClojureScript is based on value. Another +interesting thing is that with-meta creates another object of the same type +and value as the given one and attaches the given metadata to it.

-

Another open question is what happens with metadata when deriving new values from a -persistent data structure. Let’s find out:

+

Another open question is what happens with metadata when deriving new values +from a persistent data structure. Let’s find out:

-
(def derived-map (assoc map-with-metadata :language "Clojure"))
+
(def derived-map (assoc map-with-metadata :language "Clojure"))
 ;; => {:language "Clojure"}
 
 (meta derived-map)
@@ -7208,16 +7489,16 @@ 

5.3.2. Values

-

As you can see in the example above, metadata is preserved in derived versions of -persistent data structures. There are some subtleties, though. As long as the +

As you can see in the example above, metadata is preserved in derived versions +of persistent data structures. There are some subtleties, though. As long as the functions that derive new data structures return collections with the same type, metadata will be preserved; this is not true if the types change due to the -transformation. To ilustrate this point, let’s see what happens when we derive a seq -or a subvector from a vector:

+transformation. To ilustrate this point, let’s see what happens when we derive a +seq or a subvector from a vector:

-
(def v (with-meta [0 1 2 3] {:foo :bar}))
+
(def v (with-meta [0 1 2 3] {:foo :bar}))
 ;; => [0 1 2 3]
 
 (def sv (subvec v 0 2))
@@ -7234,13 +7515,14 @@ 

5.3.2. Values

5.3.3. Syntax for metadata

-

The ClojureScript reader has syntactic support for metadata annotations, which can be -written in different ways. We can prepend var definitions or collections with a caret -(^) followed by a map for annotating it with the given metadata map:

+

The ClojureScript reader has syntactic support for metadata annotations, which +can be written in different ways. We can prepend var definitions or collections +with a caret (^) followed by a map for annotating it with the given metadata +map:

-
(def ^{:doc "The answer to Life, Universe and Everything."} answer-to-everything 42)
+
(def ^{:doc "The answer to Life, Universe and Everything."} answer-to-everything 42)
 ;; => 42
 
 (meta #'answer-to-everything)
@@ -7264,8 +7546,8 @@ 

5.3.3.

-

Notice how the metadata given in the answer-to-everything var definition is merged -with the var metadata.

+

Notice how the metadata given in the answer-to-everything var definition is +merged with the var metadata.

-
(def ^:dynamic *foo* 42)
+
(def ^:dynamic *foo* 42)
 ;; => 42
 
 (:dynamic (meta #'*foo*))
@@ -7291,12 +7573,12 @@ 

5.3.3.

There is another shorthand notation for attaching metadata. If we use a caret followed by a symbol it will be added to the metadata map under the :tag -key. Using tags such as ^boolean gives the ClojureScript compiler hints about the -type of expressions or function return types.

+key. Using tags such as ^boolean gives the ClojureScript compiler hints about +the type of expressions or function return types.

-
(defn ^boolean will-it-blend? [_] true)
+
(defn ^boolean will-it-blend? [_] true)
 ;; => #<function ... >
 
 (:tag (meta #'will-it-blend?))
@@ -7313,13 +7595,13 @@ 

-
(extend-type PersistentQueue
+
(extend-type PersistentQueue
   IFn
   (-invoke
     ([this idx]
@@ -7410,7 +7692,7 @@ 

5.4.2. Printing

-
(deftype Pair [fst snd])
+
(deftype Pair [fst snd])
@@ -7421,7 +7703,7 @@

5.4.2. Printing

-
(extend-type Pair
+
(extend-type Pair
   IPrintWithWriter
   (-pr-writer [p writer _]
     (-write writer (str "#<Pair " (.-fst p) "," (.-snd p) ">"))))
@@ -7438,7 +7720,7 @@

5.4.3. Sequences

-
(extend-type Pair
+
(extend-type Pair
   ISeq
   (-first [p]
     (.-fst p))
@@ -7463,7 +7745,7 @@ 

5.4.3. Sequences

-
(def p (Pair. 1 2))
+
(def p (Pair. 1 2))
 
 (next p)
 ;; => (2)
@@ -7488,7 +7770,7 @@ 

5.4.3. Sequences

-
(def p (Pair. 1 2))
+
(def p (Pair. 1 2))
 
 (extend-type Pair
   ISeqable
@@ -7505,7 +7787,7 @@ 

5.4.3. Sequences

-
(def p (Pair. 1 2))
+
(def p (Pair. 1 2))
 ;; => #<Pair 1,2>
 
 (map inc p)
@@ -7532,7 +7814,7 @@ 

5.4.4. Collections<

-
(extend-type Pair
+
(extend-type Pair
   ILookup
   (-lookup
     ([p k]
@@ -7673,7 +7955,7 @@ 

5.4.5. Associative<

-
(extend-type Pair
+
(extend-type Pair
   IAssociative
   (-contains-key? [_ k]
     (contains? #{0 1} k))
@@ -7708,7 +7990,7 @@ 

5.4.5. Associative<

-
(extend-type Pair
+
(extend-type Pair
   IMap
   (-dissoc [p k]
     (case k
@@ -7740,7 +8022,7 @@ 

5.4.5. Associative<

-
(key [:foo :bar])
+
(key [:foo :bar])
 ;; => :foo
 
 (val [:foo :bar])
@@ -7756,7 +8038,7 @@ 

5.4.5. Associative<

-
(extend-type Pair
+
(extend-type Pair
   IMapEntry
   (-key [p]
     (.-fst p))
@@ -7786,7 +8068,7 @@ 

5.4.6. Comparison

-
(def p  (Pair. 1 2))
+
(def p  (Pair. 1 2))
 (def p' (Pair. 1 2))
 (def p'' (Pair. 1 2))
 
@@ -7823,7 +8105,7 @@ 

5.4.6. Comparison

-
(extend-type Pair
+
(extend-type Pair
   IComparable
   (-compare [p other]
     (let [fc (compare (.-fst p) (.-fst other))]
@@ -7857,7 +8139,7 @@ 

5.4.7. Metadata

-
(deftype Pair [fst snd meta]
+
(deftype Pair [fst snd meta]
   IMeta
   (-meta [p] meta)
 
@@ -7910,7 +8192,7 @@ 
-
(defprotocol MaybeMutable
+
(defprotocol MaybeMutable
   (mutable? [this] "Returns true if the value is mutable."))
 
 (extend-type default
@@ -7940,7 +8222,7 @@ 
@@ -8001,7 +8283,7 @@
-
(extend-type PersistentHashSet
+
(extend-type PersistentHashSet
   IEncodeJS
   (-clj->js [s]
     (js/Set. (into-array (map clj->js s)))))
@@ -8036,7 +8318,7 @@ 
-
(extend-type js/Set
+
(extend-type js/Set
   IEncodeClojure
   (-js->clj [s options]
     (into #{} (map js->clj (es6-iterator-seq (.values s))))))
@@ -8071,7 +8353,7 @@ 

5.4.9. Reductions

-
(reduce + #js [1 2 3])
+
(reduce + #js [1 2 3])
 ;; => 6
 
 (transduce (map inc) conj [] [1 2 3])
@@ -8086,7 +8368,7 @@ 

5.4.9. Reductions

-
(extend-type js/Set
+
(extend-type js/Set
   IReduce
   (-reduce
    ([s f]
@@ -8116,7 +8398,7 @@ 

5.4.9. Reductions

-
(reduce-kv (fn [acc k v]
+
(reduce-kv (fn [acc k v]
              (conj acc [k v]))
            []
            {:foo :bar
@@ -8131,7 +8413,7 @@ 

5.4.9. Reductions

-
(extend-type js/Map
+
(extend-type js/Map
   IKVReduce
   (-kv-reduce [m f init]
    (let [it (.entries m)]
@@ -8159,7 +8441,7 @@ 

5.4.9. Reductions

-

5.4.10. Asynchrony

+

5.4.10. Delayed computation

There are some types that have the notion of asynchronous computation, the value they represent may not be realized yet. We can ask whether a value is realized using @@ -8172,11 +8454,11 @@

5.4.10. Asynchrony
-
(defn computation []
+
(defn computation []
   (println "running!")
   42)
 
-(def d (Delay. computation nil))
+(def d (delay (computation)))
 
 (realized? d)
 ;; => false
@@ -8195,97 +8477,6 @@ 

5.4.10. Asynchrony

Both realized? and deref sit atop two protocols: IPending and IDeref.

-
-

ES6 introduced a type that captures the notion of an asynchronous computation that -might fail: the Promise. A Promise represents an eventual value and can be in one -of three states:

-
-
-
    -
  • -

    pending: there is still no value available for this computation.

    -
  • -
  • -

    rejected: an error occurred and the promise contains a value that indicates -the error.

    -
  • -
  • -

    resolved: the computation succesfully executed and the promise contains a -value with the result.

    -
  • -
-
-
-

Since the ES6 defined interface for promises doesn’t support querying its state -we’ll use Bluebird library’s Promise type for the examples. You can use Bluebird’s -promise type with the Promesa library.

-
-
-

First of all we’ll add the ability to check if a promise is realized (either -resolved or rejected) using the realized? predicate. We just have to implement the -IPending protocol:

-
-
-
-
(require '[promesa.core :as p])
-
-(extend-type js/Promise
-  IPending
-  (-realized? [p]
-    (not (.isPending p))))
-
-
-(p/promise (fn [resolve reject]))
-;; => #<Promise {:status :pending}>
-
-(realized? (p/promise (fn [resolve reject])))
-;; => false
-
-(p/resolved 42)
-;; => #<Promise {:status :resolved, :value 42}>
-
-(realized? (p/resolved 42))
-;; => true
-
-(p/rejected (js/Error. "OH NO"))
-;; => #<Promise {:status :rejected, :error #object[Error Error: OH NO]}>
-
-(realized? (p/rejected (js/Error. "OH NO")))
-;; => true
-
-
-
-

Now we’ll extend promises to be derefable. When a promise that is still pending is -dereferenced we will return a special keyword: :promise/pending. If it’s not we’ll -just return the value it contains, be it an error or a result:

-
-
-
-
(require '[promesa.core :as pro])
-
-(extend-type js/Promise
-  IDeref
-  (-deref [p]
-    (cond
-      (.isPending p)
-      :promise/pending
-
-      (.isRejected p)
-      (.reason p)
-
-      :else
-      (.value p))))
-
-@(p/promise (fn [resolve reject]))
-;; => :promise/pending
-
-@(p/resolved 42)
-;; => 42
-
-@(p/rejected (js/Error. "OH NO"))
-;; => #object[Error Error: OH NO]
-
-

5.4.11. State

@@ -8320,7 +8511,7 @@
Atom
-
(deftype MyAtom [^:mutable state ^:mutable watches]
+
(deftype MyAtom [^:mutable state ^:mutable watches]
   IPrintWithWriter
   (-pr-writer [p writer _]
     (-write writer (str "#<MyAtom " (pr-str state) ">"))))
@@ -8349,7 +8540,7 @@ 
Atom
-
(extend-type MyAtom
+
(extend-type MyAtom
   IDeref
   (-deref [a]
     (.-state a)))
@@ -8367,7 +8558,7 @@ 
Atom
-
(extend-type MyAtom
+
(extend-type MyAtom
   IWatchable
   (-add-watch [a key f]
     (let [ws (.-watches a)]
@@ -8389,7 +8580,7 @@ 
Atom
-
(extend-type MyAtom
+
(extend-type MyAtom
   IReset
   (-reset! [a newval]
     (let [oldval (.-state a)]
@@ -8404,7 +8595,7 @@ 
Atom
-
(def a (my-atom 41))
+
(def a (my-atom 41))
 ;; => #<MyAtom 41>
 
 (add-watch a :log (fn [key a oldval newval]
@@ -8432,7 +8623,7 @@ 
Atom
-
(extend-type MyAtom
+
(extend-type MyAtom
   ISwap
   (-swap!
    ([a f]
@@ -8462,7 +8653,7 @@ 
Atom
-
(def a (my-atom 0))
+
(def a (my-atom 0))
 ;; => #<MyAtom 0>
 
 (add-watch a :log (fn [key a oldval newval]
@@ -8521,7 +8712,7 @@ 
Volatile
-
(deftype MyVolatile [^:mutable state]
+
(deftype MyVolatile [^:mutable state]
   IPrintWithWriter
   (-pr-writer [p writer _]
     (-write writer (str "#<MyVolatile " (pr-str state) ">"))))
@@ -8546,7 +8737,7 @@ 
Volatile
-
(extend-type MyVolatile
+
(extend-type MyVolatile
   IDeref
   (-deref [v]
     (.-state v))
@@ -8598,231 +8789,6 @@ 
-
Case study: the hodgepodge library
-
-

Hodgepodge is a ClojureScript library -for treating the browser’s local and session storage as if it were a transient data -structure. It allows you to insert, read and delete ClojureScript data structures -without worrying about encoding and decoding them.

-
-
-

Browser’s storage is a simple key-value store that only supports strings. Since all -of ClojureScript data structures can be dumped into a string and reified from a -string using the reader we can store arbitrary ClojureScript data -in storage. We can also extend the reader for being able to read custom data types -so we’re able to put our types in storage and hodgepodge will handle the encoding -and decoding for us.

-
-
-

We’ll start by wrapping the low-level storage methods with functions. The following -operations are supported by the storage:

-
-
-
    -
  • -

    getting the value corresponding to a key

    -
  • -
  • -

    setting a key to a certain value

    -
  • -
  • -

    removing a value given its key

    -
  • -
  • -

    counting the number of entries

    -
  • -
  • -

    clearing the storage

    -
  • -
-
-
-

Let’s wrap them in a more idiomatic API for ClojureScript:

-
-
-
-
(defn contains-key?
-  [^js/Storage storage ^string key]
-  (let [ks (.keys js/Object storage)
-        idx (.indexOf ks key)]
-    (>= idx 0)))
-
-(defn get-item
-  ([^js/Storage storage ^string key]
-     (get-item storage key nil))
-  ([^js/Storage storage ^string key ^string default]
-     (if (contains-key? storage key)
-       (.getItem storage key)
-       default)))
-
-(defn set-item
-  [^js/Storage storage ^string key ^string val]
-  (.setItem storage key val)
-  storage)
-
-(defn remove-item
-  [^js/Storage storage ^string key]
-  (.removeItem storage key)
-  storage)
-
-(defn length
-  [^js/Storage storage]
-  (.-length storage))
-
-(defn clear!
-  [^js/Storage storage]
-  (.clear storage))
-
-
-
-

Nothing too interesting going on there, we just wrapped the storage methods in a -nicer API. Now we will define a couple functions for serializing and deserializing -ClojureScript data structures to strings:

-
-
-
-
(require '[cljs.reader :as reader])
-
-(defn serialize [v]
-  (binding [*print-dup* true
-            *print-readably* true]
-    (pr-str v)))
-
-(def deserialize
-  (memoize reader/read-string))
-
-
-
-

The serialize function is used for converting a ClojureScript data structure into -a string using the pr-str function, configuring a couple dynamic variables for -obtaining the desired behaviour:

-
-
-
    -
  • -

    print-dup is set to true for a printed object to preserve its type -when read later

    -
  • -
  • -

    print-readably is set to true for not converting non-alphanumeric -characters to their escape sequences

    -
  • -
-
-
-

The deserialize function simply invokes the reader’s function for reading a string -into a ClojureScript data structure: read-string. It’s memoized for not having to -call the reader each time we deserialize a string since we can assume that a -repeated string always corresponds to the same data structure.

-
-
-

Now we can start extending the browser’s Storage type for acting like a transient -data structure. Take into account that the Storage type is only available in -browsers. We’ll start by implementing the ICounted protocol for counting the items -in the storage, we’ll simply delegate to our previously defined length function:

-
-
-
-
(extend-type js/Storage
-  ICounted
-  (-count [^js/Storage s]
-   (length s)))
-
-
-
-

We want to be able to use assoc! and dissoc! for inserting and deleting -key-value pairs in the storage, as well as the ability to read from it. We’ll -implement the ITransientAssociative protocol for assoc!, ITransientMap for -dissoc! and ILookup for reading storage keys.

-
-
-
-
(extend-type js/Storage
-  ITransientAssociative
-  (-assoc! [^js/Storage s key val]
-    (set-item s (serialize key) (serialize val))
-    s)
-
-  ITransientMap
-  (-dissoc! [^js/Storage s key]
-    (remove-item s (serialize key))
-    s)
-
-  ILookup
-  (-lookup
-    ([^js/Storage s key]
-       (-lookup s key nil))
-    ([^js/Storage s key not-found]
-       (let [sk (serialize key)]
-         (if (contains-key? s sk)
-           (deserialize (get-item s sk))
-           not-found)))))
-
-
-
-

Now we’re able to perform some operations on either session or local storage, let’s -give them a try:

-
-
-
-
(def local-storage js/localStorage)
-(def session-storage js/sessionStorage)
-
-(assoc! local-storage :foo :bar)
-
-(:foo local-storage)
-;; => :bar
-
-(dissoc! local-storage :foo)
-
-(get local-storage :foo)
-;; => nil
-
-(get local-storage :foo :default)
-;; => :default
-
-
-
-

Finally, we want to be able to use conj! and persistent! on local storage so we -must implement the ITransientCollection protocol, let’s give it a go:

-
-
-
-
(extend-type js/Storage
-  ITransientCollection
-  (-conj! [^js/Storage s ^IMapEntry kv]
-    (assoc! s (key kv) (val kv))
-    s)
-
-  (-persistent! [^js/Storage s]
-    (into {}
-          (for [i (range (count s))
-                :let [k (.key s i)
-                      v (get-item s k)]]
-            [(deserialize k) (deserialize v)]))))
-
-
-
-

conj! simply obtains the key and value from the map entry and delegates to -assoc!. persistent! deserializes every key-value pair in the storage and returns -an immutable snapshot of the storage as a ClojureScript map. Let’s try it out:

-
-
-
-
(clear! local-storage)
-
-(persistent! local-storage)
-;; => {}
-
-(conj! local-storage [:foo :bar])
-(conj! local-storage [:baz :xyz])
-
-(persistent! local-storage)
-;; => {:foo :bar, :baz :xyz}
-
-
-
-
Transient vectors and sets

We’ve learned about most of the protocols for transient data structures but we’re @@ -8835,7 +8801,7 @@

-
(extend-type array
+
(extend-type array
   ITransientAssociative
   (-assoc! [arr key val]
     (if (number? key)
@@ -8868,7 +8834,7 @@ 
-
(require '[cljs.core.async :refer [chan put! take!]])
+
(require '[cljs.core.async :refer [chan put! take!]])
 
 (enable-console-print!)
 
@@ -8960,7 +8926,7 @@ 

5.5.1. Channels

-
(require '[cljs.core.async :refer [chan put! take!]])
+
(require '[cljs.core.async :refer [chan put! take!]])
 
 (def ch (chan))
 
@@ -8987,7 +8953,7 @@ 

5.5.1. Channels

-
(require '[cljs.core.async :refer [chan put! close!]])
+
(require '[cljs.core.async :refer [chan put! close!]])
 
 (def ch (chan))
 
@@ -9005,7 +8971,7 @@ 

5.5.1. Channels

-
(require '[cljs.core.async :refer [chan put! take! close!]])
+
(require '[cljs.core.async :refer [chan put! take! close!]])
 
 (def ch (chan))
 
@@ -9027,7 +8993,7 @@ 

5.5.1. Channels

-
(require '[cljs.core.async :refer [chan put!]])
+
(require '[cljs.core.async :refer [chan put!]])
 
 (def ch (chan))
 
@@ -9044,7 +9010,7 @@ 
Buffers
-
(require '[cljs.core.async :refer [chan put! take!]])
+
(require '[cljs.core.async :refer [chan put! take!]])
 
 (def ch (chan))
 
@@ -9074,7 +9040,7 @@ 
Buffers
-
(require '[cljs.core.async :refer [chan put! take!]])
+
(require '[cljs.core.async :refer [chan put! take!]])
 
 (def ch (chan 1))
 
@@ -9117,7 +9083,7 @@ 
Fixed
-
(require '[cljs.core.async :refer [chan buffer]])
+
(require '[cljs.core.async :refer [chan buffer]])
 
 (def a-ch (chan 32))
 
@@ -9138,7 +9104,7 @@ 
Dropping
-
(require '[cljs.core.async :refer [chan dropping-buffer put! take!]])
+
(require '[cljs.core.async :refer [chan dropping-buffer put! take!]])
 
 (def ch (chan (dropping-buffer 2)))
 
@@ -9176,7 +9142,7 @@ 
Sliding
-
(require '[cljs.core.async :refer [chan sliding-buffer put! take!]])
+
(require '[cljs.core.async :refer [chan sliding-buffer put! take!]])
 
 (def ch (chan (sliding-buffer 2)))
 
@@ -9221,7 +9187,7 @@ 
Transducers
-
(require '[cljs.core.async :refer [chan put! take!]])
+
(require '[cljs.core.async :refer [chan put! take!]])
 
 (def ch (chan 1 (map inc)))
 
@@ -9240,7 +9206,7 @@ 
Transducers
-
(require '[cljs.core.async :refer [chan put! take!]])
+
(require '[cljs.core.async :refer [chan put! take!]])
 
 (def ch (chan 1 (take 2)))
 
@@ -9285,7 +9251,7 @@ 
Handlin
-
(require '[cljs.core.async :refer [chan put! take!]])
+
(require '[cljs.core.async :refer [chan put! take!]])
 
 (enable-console-print!)
 
@@ -9330,7 +9296,7 @@ 
Offer and Poll
-
(require '[cljs.core.async :refer [chan offer!]])
+
(require '[cljs.core.async :refer [chan offer!]])
 
 (def ch (chan 1))
 
@@ -9348,7 +9314,7 @@ 
Offer and Poll
-
(require '[cljs.core.async :refer [chan offer! poll!]])
+
(require '[cljs.core.async :refer [chan offer! poll!]])
 
 (def ch (chan 1))
 
@@ -9382,7 +9348,7 @@ 

5.5.2. Processes

-
(require '[cljs.core.async :refer [chan <! >!]])
+
(require '[cljs.core.async :refer [chan <! >!]])
 (require-macros '[cljs.core.async.macros :refer [go]])
 
 (enable-console-print!)
@@ -9429,7 +9395,7 @@ 

5.5.2. Processes

-
(require '[cljs.core.async :refer [<! timeout]])
+
(require '[cljs.core.async :refer [<! timeout]])
 (require-macros '[cljs.core.async.macros :refer [go]])
 
 (enable-console-print!)
@@ -9474,7 +9440,7 @@ 
Choice
-
(require '[cljs.core.async :refer [chan <! timeout alts!]])
+
(require '[cljs.core.async :refer [chan <! timeout alts!]])
 (require-macros '[cljs.core.async.macros :refer [go]])
 
 (enable-console-print!)
@@ -9528,7 +9494,7 @@ 
Choice
-
(require '[cljs.core.async :refer [chan <! alts!]])
+
(require '[cljs.core.async :refer [chan <! alts!]])
 (require-macros '[cljs.core.async.macros :refer [go]])
 
 (enable-console-print!)
@@ -9578,7 +9544,7 @@ 
Priority
-
(require '[cljs.core.async :refer [chan >! alts!]])
+
(require '[cljs.core.async :refer [chan >! alts!]])
 (require-macros '[cljs.core.async.macros :refer [go]])
 
 (enable-console-print!)
@@ -9628,7 +9594,7 @@ 
Defaults
-
(require '[cljs.core.async :refer [chan alts!]])
+
(require '[cljs.core.async :refer [chan alts!]])
 (require-macros '[cljs.core.async.macros :refer [go]])
 
 (def a-ch (chan))
@@ -9670,7 +9636,7 @@ 
pipe
-
(require '[cljs.core.async :refer [chan pipe put! <! close!]])
+
(require '[cljs.core.async :refer [chan pipe put! <! close!]])
 (require-macros '[cljs.core.async.macros :refer [go-loop]])
 
 (def in (chan))
@@ -9723,7 +9689,7 @@ 
pipeline-async
-
(require '[cljs.core.async :refer [chan pipeline-async put! <! close!]])
+
(require '[cljs.core.async :refer [chan pipeline-async put! <! close!]])
 (require-macros '[cljs.core.async.macros :refer [go-loop]])
 
 (def in (chan))
@@ -9775,7 +9741,7 @@ 
pipeline
-
(require '[cljs.core.async :refer [chan pipeline put! <! close!]])
+
(require '[cljs.core.async :refer [chan pipeline put! <! close!]])
 (require-macros '[cljs.core.async.macros :refer [go-loop]])
 
 (def in (chan))
@@ -9818,7 +9784,7 @@ 
split
-
(require '[cljs.core.async :refer [chan split put! <! close!]])
+
(require '[cljs.core.async :refer [chan split put! <! close!]])
 (require-macros '[cljs.core.async.macros :refer [go-loop]])
 
 (def in (chan))
@@ -9870,7 +9836,7 @@ 
reduce
-
(require '[cljs.core.async :as async :refer [chan put! <! close!]])
+
(require '[cljs.core.async :as async :refer [chan put! <! close!]])
 (require-macros '[cljs.core.async.macros :refer [go]])
 
 (def in (chan))
@@ -9898,7 +9864,7 @@ 
onto-chan
-
(require '[cljs.core.async :as async :refer [chan put! <! close! onto-chan]])
+
(require '[cljs.core.async :as async :refer [chan put! <! close! onto-chan]])
 (require-macros '[cljs.core.async.macros :refer [go]])
 
 (def in (chan))
@@ -9920,7 +9886,7 @@ 
to-chan
-
(require '[cljs.core.async :refer [chan put! <! close! to-chan]])
+
(require '[cljs.core.async :refer [chan put! <! close! to-chan]])
 (require-macros '[cljs.core.async.macros :refer [go-loop]])
 
 (def ch (to-chan (range 3)))
@@ -9953,7 +9919,7 @@ 
merge
-
(require '[cljs.core.async :refer [chan put! <! close! merge]])
+
(require '[cljs.core.async :refer [chan put! <! close! merge]])
 (require-macros '[cljs.core.async.macros :refer [go-loop]])
 
 (def in1 (chan))
@@ -10016,7 +9982,7 @@ 
Mult
-
(require '[cljs.core.async :refer [chan put! <! close! timeout mult tap]])
+
(require '[cljs.core.async :refer [chan put! <! close! timeout mult tap]])
 (require-macros '[cljs.core.async.macros :refer [go-loop]])
 
 ;; Source channel and mult
@@ -10086,7 +10052,7 @@ 
Pub-sub
-
(require '[cljs.core.async :refer [chan put! <! close! pub sub]])
+
(require '[cljs.core.async :refer [chan put! <! close! pub sub]])
 (require-macros '[cljs.core.async.macros :refer [go-loop]])
 
 ;; Source channel and publication
@@ -10182,7 +10148,7 @@ 
Mixer
-
(require '[cljs.core.async :refer [chan put! <! close! mix admix
+
(require '[cljs.core.async :refer [chan put! <! close! mix admix
                                    unmix toggle solo-mode]])
 (require-macros '[cljs.core.async.macros :refer [go-loop]])
 
@@ -10213,7 +10179,7 @@ 
Mixer
-
(do
+
(do
   (put! in-1 1)
   (put! in-2 2)
   (put! in-3 3))
@@ -10228,7 +10194,7 @@ 
Mixer
-
(toggle mixer {in-2 {:pause true}})
+
(toggle mixer {in-2 {:pause true}})
 ;; => true
 
 (do
@@ -10251,7 +10217,7 @@ 
Mixer
-
(toggle mixer {in-2 {:mute true}})
+
(toggle mixer {in-2 {:mute true}})
 ;; => true
 
 (do
@@ -10276,7 +10242,7 @@ 
Mixer
-
(toggle mixer {in-1 {:solo true}
+
(toggle mixer {in-1 {:solo true}
                in-2 {:solo true}})
 ;; => true
 
@@ -10299,7 +10265,7 @@ 
Mixer
-
(solo-mode mixer :pause)
+
(solo-mode mixer :pause)
 ;; => true
 (toggle mixer {in-1 {:solo true}
                in-2 {:solo true}})
@@ -10325,310 +10291,7 @@ 
Mixer
-

6. Appendix

-
-
-

6.1. Appendix A: Interactive development with Figwheel

-
-

6.1.1. Introduction

-
-

In this project, we will not do “Hello World”— that has been done to death. -Instead, this project will be a web page that asks you for your age in years and -tells you how many days that is, using an approximation of 365 days per year.

-
-
-

For this project, we will use the figwheel leiningen plugin. This plugin creates a -fully interactive, REPL-based, autoreloading environment.

-
-
-
-

6.1.2. First steps

-
-

The first step is to create the new project using the figwheel lein template. We -will name the project age, and create it by typing:

-
-
-
-
$ lein new figwheel age
-Retrieving figwheel/lein-template/0.3.5/lein-template-0.3.5.pom from clojars
-Retrieving figwheel/lein-template/0.3.5/lein-template-0.3.5.jar from clojars
-Generating fresh 'lein new' figwheel project.
-$ cd age # move into newly created project directory
-
-
-
-

The project has the following structure:

-
-
-
-
> tree age      # the linux "tree" utility displays dir structure
-age
-├── .gitignore
-├── project.clj
-├── README.md
-├── resources
-│   └── public
-│       ├── css
-│       │   └── style.css
-│       └── index.html
-└── src
-    └── age
-        └── core.cljs
-
-
-
-

The project.clj file contains information that Leiningen uses to download -dependencies and build the project. For now, just trust that everything in that file -is exactly as it should be.

-
-
-

Open the index.html file and make it look like the following:

-
-
-
-
<!DOCTYPE html>
-<html>
-  <head>
-    <link href="css/style.css" rel="stylesheet" type="text/css">
-    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
-  </head>
-  <body>
-    <div id="app">
-      <h1>Age in Days</h1>
-      <p>
-        Enter your age in years:
-        <input type="text" size="5" id="years">
-        <button id="calculate">Calculate</button>
-      </p>
-      <p id="feedback"></p>
-    </div>
-    <script src="js/compiled/age.js" type="text/javascript"></script>
-  </body>
-</html>
-
-
-
-

The core.cljs file is where all the action takes place. For now, leave it exactly -as it is, and start the figwheel environment, which will load a large number of -dependencies and start a server.

-
-
-
-
$ lein figwheel
-Retrieving lein-figwheel/lein-figwheel/0.5.2/lein-figwheel-0.5.2.pom from clojars
-Retrieving figwheel-sidecar/figwheel-sidecar/0.5.2/figwheel-sidecar-0.5.2.pom from clojars
-Retrieving org/clojure/clojurescript/1.7.228/clojurescript-1.7.228.pom from central
-... # much more output
-Prompt will show when Figwheel connects to your application
-
-
-
-

If you are using Linux or Mac OS X, type the command as rlwrap lein figwheel. -In your browser, go to URL http://localhost:3449, and you will see something -like the following screenshot if you open up the web console.

-
-
-
-Screenshot of web page and console -
-
-
-

The terminal will then give you a REPL prompt:

-
-
-
-
$ rlwrap lein figwheel
-To quit, type: :cljs/quit
-cljs.user=>
-
-
-
-

For now, do what it says in the core.cljs file — change the (println…​) and -then save the file. When you do so, you will see the change reflected immediately in -the browser.

-
-
-

Then make an error by adding an extra closing parenthesis to the println. When you -save the file, will see a compile error in the browser window.

-
-
-
-

6.1.3. Interacting with JavaScript

-
-

In the REPL window, type the following to invoke JavaScript’s window.alert() -function:

-
-
-
-
(.alert js/window "It works!")
-;; => nil
-
-
-
-

The general format for invoking a JavaScript function from ClojureScript is to give -the function name (preceded by a dot), the object that “owns” the function, and any -parameters to that function. You should see an alert appear in your browser window; -when you dismiss the alert, the REPL will print nil and give you another -prompt. You can also do it this way:

-
-
-
-
(js/alert "It works!")
-;; => nil
-
-
-
-

However, the first version always works, so, for consistency, we will use that -notation throughout this tutorial.

-
-
-

JavaScript objects may be instanciated from ClojureScript using the same interop -syntax as for Java/Clojure interop, using the class name followed by a dot. -JavaScript methods may also be called using the familiar interop syntax:

-
-
-
-
> (def d (js/Date.))
-;; => #'cljs.user/d
-> d
-;; => #inst "2016-04-03T21:04:29.908-00:00"
-> (.getFullYear d)
-;; => 2016
-
-> (.toUpperCase "doh!")
-;; => "DOH!"
-
-> (.getElementById js/document "years")
-;; => #object[HTMLInputElement [object HTMLInputElement]]
-
-
-
-

The next example shows where we’re headed. To retrieve an object’s property, use the -dot-dash or "access" syntax .- before the property name. In the browser window, -type a number into the input field (in the example, we typed 24), then do this in -the REPL.

-
-
-
-
(def year-field (.getElementById js/document "years"))
-;; => #'cljs.user/year-field
-
-(.-value year-field)
-;; => "24"
-
-(set! (.-value year-field) "25")
-;; => "25"
-
-
-
-

This works, but it is little more than a direct translation of JavaScript to -ClojureScript. The next step is to add event handling to the button. Event handling -is loaded with all sorts of cross-platform compatibility issues, so we’d like a step -up from plain ClojureScript.

-
-
-

The solution is the Google Closure library. To use it, you have to modify the -:require clause at the beginning of core.cljs:

-
-
-
-
(ns ^:figwheel-always age.core
-  (:require [goog.dom :as dom]
-            [goog.events :as events]))
-
-
-
-

Getting an element and setting its value is now slightly easier. Do this in the REPL -and see the results in the browser window.

-
-
-
-
(in-ns 'age.core)
-(def y (dom/getElement "years"))
-;; => #'age.core/y
-
-(set! (.-value y) "26")
-;; => "26"
-
-(dom/setTextContent (dom/getElement "feedback") "This works!")
-;; => nil
-
-
-
-

To add an event, you define a function that takes a single argument (the event to be -handled), and then tell the appropriate HTML element to listen for it. The -events/listen function takes three arguments: the element to listen to, the event -to listen for, and the function that will handle the event.

-
-
-
-
(defn testing [evt] (js/alert "Responding to click"))
-;; => #'age.core/testing
-
-(events/listen (dom/getElement "calculate") "click" testing)
-;; => #<[object Object]>
-
-
-
-

After doing this, the browser should respond to a click on the button. If you would -like to remove the listener, use unlisten.

-
-
-
-
(events/unlisten (dom/getElement "calculate") "click" testing)
-;; => true
-
-
-
-

Now, put that all together in the core.cljs file as follows:

-
-
-
-
(ns ^:figwheel-always age.core
-  (:require [goog.dom :as dom]
-            [goog.events :as events]))
-
-(enable-console-print!)
-
-(defn calculate
-  [event]
-  (let [years (.parseInt js/window (.-value (dom/getElement "years")))
-        days (* 365 years)]
-    (dom/setTextContent (dom/getElement "feedback")
-                        (str "That is " days " days old."))))
-
-(defn on-js-reload [])
-
-(events/listen (dom/getElement "calculate") "click" calculate)
-
-
-
-
- -
-
-
-

7. Acknowledgments

+

6. Acknowledgments

Special thanks to:

@@ -10691,7 +10354,7 @@

7. Acknowledgme

-

8. Further Reading

+

7. Further Reading

Here is a list of more resources about ClojureScript.

@@ -10724,8 +10387,79 @@

8. Further Read + \ No newline at end of file