1. Introduction
This chapter is an introduction to the Clojure ecosystem, and intends to explain the philosophy behind it.
1.1. First contact
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 Nashhorn.
Unlike other languages that intend to compile to JavaScript (like TypeScript, FunScript or CoffeeScript), ClojureScript is designed to use JavaScript like bytecode. It embraces functional programming, and has very safe and consistent defaults.
Another big difference (and in my opinion a big 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 easy adapted to differences between its execution environments.
TBD
2. The language.
This chapter will be a little introduction to ClojureScript without assumptions about previous knowledge of the Clojure language, providing a quick tour over all the things you will need to know in order to understand the rest of this book.
2.1. First steps with lisp syntax
Invented by John McCarthy in 1958, Lisp is one of the oldest programming languages that is still around. It has evolved into a whole lot of derivatives called dialects and ClojureScript is one of them. It’s a programming language written in its own data structures, originally lists enclosed in parenthesis, but Clojure(Script) has evolved the Lisp syntax with more data structures making it more pleasant to write and read.
A list with a function in the first position is used for calling a function in ClojureScript:
(+ 1 2 3)
;; => 6
In the example above we’re applying the addition function + to the arguments 1, 2 and 3. ClojureScript
allows many unusual characters like ? or - in symbol names which makes it easier to read:
(zero? 0)
;; => true
For distinguishing function calls and lists, we can quote lists to keep them from being evaluated. The quoted lists will be treated as data instead of as a function call:
'(+ 1 2 3)
;; => (+ 1 2 3)
ClojureScript uses more than lists for its syntax, the full details will be covered later but here is an example of the usage of a vector (enclosed in brackets) for defining local bindings:
(let [x 1
y 2
z 3]
(+ x y x))
;; => 6
This is practically all the syntax we need to know for using not only ClojureScript, but any Lisp. Being written in its own data structures (often referred to as homoiconicity) is a great property since the syntax is uniform and simple; also, code generation via macros is easier than in any language, giving us plenty of power for extending the language to suit our needs.
2.2. The base data types.
The ClojureScript language has a rich set of data types like most programmig languages. It provides scalar datatypes that will be very familiar for you such as numbers, strings, floats. Beyond these it also provides a great amount of others that might be less familiar such as symbols, keywords, regex, vars, atoms, volatiles…
ClojureScript embrases the host language, and where possible it uses the hosts provided types. In this case: numbers and strings are used as is and they behave in same way as in javascript.
2.2.1. Numbers
In ClojureScript numbers include both integers and floating points. Keeping in mind that ClojureScript is a guest language that compiles to javascript, integers are actually javascripts native floating points under the hood.
Like in any other language, numbers in ClojureScript are represented in following way:
23
+23
-100
1.7
-2
33e8
12e-14
3.2e-4
2.2.2. Keywords
Keywords in ClojureScript are objects that always evaluate to themselves. They are usually used in map data structures to efficiently reprensent the keys.
:foobar
:2
:?
:foo/bar
As you can see, the keywords are all prefixed with :, but this char 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 a function keyword. Do not worry if you don’t understand
or are unclear about anything in the following example, the functions are discussed in later chapters.
(keyword "foo")
;; => :foo
2.2.3. Symbols
Symbols in ClojureScript are very very similar to now known Keywords. But them instead of evaluating to themselves, are evalutated to something that them refers, that can be function, variables, …
Them are represented with something that not start with a number
sample-symbol
othersymbol
f1
Don’t worry if you don’t understand right away, symbols are used in almost all our examples which will give you the opportunity to learn more as we go on.
2.2.4. Strings
Nothing new we can explain about strings that you don’t already know. In ClojureScript they work the same as in any other language. One point of interest however is that they are immutable.
In this case they are the same as in javascript:
"A example of a string"
The pecularity of Strings in ClojureScript is due to it’s lisp syntax, single and multiline strings have the same syntax:
"This is a multiline
string in ClojureScript."
2.2.5. Characters
ClojureScript also lets you write single characters using Clojure’s character literal syntax.
\a ; The lowercase a character
\newline ; The new line character
Since the host language doesn’t contain character literals, ClojureScript characters are transformed behind the scenes into single character strings.
2.2.6. Collections
Another big step in explaining a language, is to explain it’s collections and collection abstractions. ClojureScript is not an exception in this rule.
ClojureScript comes with many types of different collections. The main difference between ClojureScript collections and collections in other languages is that they are persistent and immutable.
Before moving on to all of these (possibly) unknown concepts, we’ll present a high level overview of existing collection types in ClojureScript.
Lists
This is a clasic collection type in languages based on lisp. Lists are the simplest type of collection in ClojureScript. Lists can contain items of any type, including other collections.
Lists in ClojureScript are repsesented by items enclosed between parenthesis:
'(1 2 3 4 5)
'(:foo :bar 2)
As you can observe, all list examples are prefixed with the ' char. This is because lists in lisp like
languages are often used to express things like function or macro calls. In that case
the first item should be a symbol that will evaluate to a something callable and the rest of the list
elemenents will be function parameters.
(inc 1)
;; => 2
'(inc 1)
;; => (inc 1)
As you see, if you will evaluate the (inc 1) without prefixing it with ' char, it will resolve
the inc symbol to the inc function and will execute it with 1 as first parameter. Resulting
in a 2 as return value.
Lists have the pecularity that they are very efficient if you access to it in a sequence mode or access to its first elements but are not very good option if you need random (index) acces to its elements.
Vectors
Like lists, Vectors store a series of values, but in this case with very efficient index access to its elements and its elements in difference with list are evaluated in order. Do not worry, in below chapters we’ll go depth in details but at this moment is more that enough.
Vectors uses square brakets for the literal syntax, let see some examples:
[:foo :bar]
[3 4 5 nil]
Like lists, vectors can contain objects of any type, as you can observe the previos example.
Maps
Maps is a collection abstraction that allows store unique keys associated with one value. In other languages are commonly known as hash-maps or dicts. Maps in ClojureScript uses a curly braces as literal syntax.
{:foo "bar", :baz 2}
{:foobar [:a :b :c]}
|
Note
|
Commas are frequently used for separate a key value pair but are completelly optional. In ClojureScript syntax, comas are treated like spaces. |
Like Vectors, every item in a map literal is evaluated before the result is stored in a map, but the order of evaluation is not guaranteed.
Sets
And finally, Sets.
Sets stores in an unordered way zero or more unique items of any type. They,
like maps, uses curly braces for its literal syntax with difference that uses a # as leading
character:
#{1 2 3 :foo :bar}
In below chapters we’ll go depth in sets and other collection types explained in this chapter.
2.3. Vars
ClojureScript is a mostly functional language and focused in immutability. Becuase of that, it does not has the concept of variables. The most closest analogy to variables are vars. The vars are represented by symbols and stores a single value together with metadata.
You can define a var using a def special form:
(def x 22)
(def y [1 2 3])
The vars are always top level in the namespace. If you use def in a function call, the var will be defined
at the namespace level.
2.4. Functions
2.4.1. The first contact
It’s time of make things happen. In ClojureScript, a function are first-class type. It behaves like any other type, you can pass it as parameter, you can return it as value, always respecting the lexical scope. ClojureScript also has some features from dynamic scope but this will be discused in other section.
If you want know more about scopes, this wikipedia article is ver extensive and explain very well different types of scope.
As ClojureScript is a lisp dialect, it uses the prefix notation for calling a function:
(inc 1)
;; => 2
The inc is a function and is part of ClojureScript runtime, and 1 is a first positional
argument for the inc function.
(+ 1 2 3)
;; => 6
The + symbol represents a add function, in ALGOL type of languages is an operator and only
allows two parameters.
The prefix notation has huge advantages, some of them not alwats obvious. ClojureScript does not has distinction between a function and operator, everything is a function. The inmediate advantage is that the prefix notation allows an arbitrary number of arguments per "operator". Also, it eliminates per complete the problem of operator precedence.
2.4.2. Defining own functions
The function can be defined with fn special form. This is aspect of function definition:
(fn [param1 param2]
(+ (inc param1) (inc param2)))
You can define a function and call it in same time (in a single expression):
((fn [x] (inc x)) 1)
;; => 2
Let start creating named functions. But that is means named function really? Is very simple, as in ClojureScript functions are fist-class and behaves like any other value, naming a function is just store it in a var:
(def myinc (fn [x] (+ x 1)))
(myinc 1)
;; => 2
ClojureScript also offers the defn macro as a little sugar syntax for make function definition
more idiomatic:
(defn myinc
"Self defined version of `inc`."
[x]
(+ x 1))
2.4.3. Function with multiple arities
ClojureScript also comes with ability to define functions with arbitrary number of arities. The syntax is almost the same as define standard function with the difference that it has more that one body.
Let see an example, surelly it will explain it much better:
(defn myinc
"Self defined version of parametrized `inc`."
([x] (myinc x 1))
([x increment]
(+ x increment)))
And there some examples using the previously defined multi arity function. I can observe that if you call a function with wrong number of parameters the compiler will emit an error about that:
(myinc 1)
;; => 1
(myinc 1 3)
;; => 4
(myinc 1 3 3)
;; Compiler error
|
Note
|
Explaining the "arity" is out of scope of this book, however you can read about that in this wikipedia article. |
2.4.4. Variadic functions
An other way to accept multiple parameters is defining variadic functions. Variadic functions are functions that will be able accept arbitrary number of arguments:
(defn my-variadic-set
[& params]
(set params))
(my-variadic-set 1 2 3 1)
;; => #{1 2 3}
The way to denone a variadic function is using the & simbol prefix on its arguments vector.
2.4.5. Short syntax for anonymous functions
ClojureScript provides a shorter syntax for define anonymos (and almost always one liner) functions
using the #() reader macro. Reader macros are "special" expressions that in compile time will be
transformed to the apropiate language form. In this case to some expression that uses fn special
form.
(def my-set #(set %1 %2))
(my-set 1 2)
;; => #{1 2}
The %1, %2, %N are simple markers for parameter positions that are implicitly declared when
the reader macro will be interpreted and converted to fn expression.
Also, if a function only accepts one argument, you can ommit the number after % symbol, the
function #(set %1) can be written #(set %).
Additionaly, this syntax also supports the variadic form with %& symbol:
(def my-variadic-set #(set %&))
(my-variadic-set 1 2 2)
;; => #{1 2}
2.5. Flow control
ClojureScript has a great different approaches for flow control.
2.5.1. Branching with if
Let start with a basic one: if. In ClojureScript the if is an expression and not an
statement, and it has three parametes: first one the condition expression, the second one
a expression that will evalute if a condition expression will evalute in a logical true,
and the third one will evaluated otherwise.
(defn mypos?
[x]
(if (pos? x)
"positive"
"negative"))
(mypos? 1)
;; => "positive"
(mypos? -1)
;; => "negative"
If you want do more that one thing in one of two expressions, you can use block expression do, that
is will explained in next section.
2.5.2. Branching with cond
Sometimes, the if expression can be slightly limited because it does not have the "else if" part
for add more that one condition. The cond comes to the rescue.
With cond expression, you can define multiple conditions:
(defn mypos?
[x]
(cond
(> x 0) "positive"
(< x 0) "negative"
:else "zero"))
(mypos? 0)
;; => "zero"
(mypos? -2)
;; => "negative"
Also, cond has an other form, called condp, that works very similar to the simple cond
but looks more cleaner when a predicate is always the same for all conditions:
(defn translate-lang-code
[code]
(condp = (keyword code)
:es "Spanish"
:en "English"
"Unknown"))
(translate-lang-code "en")
;; => "English"
(translate-lang-code "fr")
;; => "Unknown"
2.5.3. Branching with case
The case branching expression has very similar use case that our previous example with
condp. The main difference is that, case always uses the = predicate/function and its
branching values are evaluated at compile time. This results in a more prerformant form
that cond or condp but has the disadvantage of that the condition value should be
a static value.
Let see the same example as previous one but using case:
(defn translate-lang-code
[code]
(case code
"es" "Spanish"
"es" "English"
"Unknown"))
(translate-lang-code "en")
;; => "English"
(translate-lang-code "fr")
;; => "Unknown"
2.6. Locals, Blocks and Loops
2.6.1. Locals
ClojureScript does not has the variables concepts, but it does have locals. Locals as per usual, are immutable and if you try mutate them, the compiller will throw an error.
The locals are defined with let expression. It starts with a vector as first parameter
followed by arbitrary number of expresions. The first parameter should contain a arbitrary
number of pairs that starts with a binding form followed of an expression whose value will
be bound to this new local for the remainer of the let expression.
(let [x (inc 1)
y (+ x 1)]
(println "Simple message from the body of a let")
(* x y))
;; Simple messages from the body of a let
;; => 6
2.6.2. Blocks
The blocks in ClojureScript can be done using the do expression and is ususally used
for side effects, like printing something in console or write a log in a logger. Something
for that the return value is not necesary.
The do expression accept as parameter an arbitrary number of other expressions but return
the return value only from the last one:
(do
(println "hello world")
(println "hola mundo")
(+ 1 2))
;; hello world
;; hola mundo
;; => 3
The let expression, explained just in previous section, the body is very similar to the
do expression. In fact, it is called that is has an implicit do.
2.6.3. Loops
The functional approach of ClojureScript, this causes that it does not have standard, well known statements based loops. The loops in clojurescript are handled using recursion. The recursion sometimes requires additional thinking about how model your problem in a slightly different way than imperative languages.
Also, many of the common patterns for which for is used in other languages are achieved
through higher-order functions.
Looping with loop/recur
Let’s take a look at how to express loops using recursions with the loop and recur forms.
loop defines a possibly empty list of bindings (notice the symmetry with let) and recur
jumps execution after the looping point with new values for those bindings.
Let’s see an example:
(loop [x 0]
(println "Looping with " x)
(if (= x 2)
(println "Done looping!")
(recur (inc x))))
;; Looping with 0
;; Looping with 1
;; Looping with 2
;; Done looping!
;; => nil
In the above snippet, we bind the name x to the value 0 and execute the body. Since the
condition is not met the first time is run we recur, incrementing the binding value with
the inc function. We do this once more until the condition is met and, since there aren’t
more recur calls, exit the loop.
Note that loop isn’t the only point we can recur too, using recur inside a function
executes the body of the function recursively with the new bindings:
(defn recursive-function [x]
(println "Looping with" x)
(if (= x 2)
(println "Done looping!")
(recur (inc x))))
(recursive-function 0)
;; Looping with 0
;; Looping with 1
;; Looping with 2
;; Done looping!
;; => nil
Replacing for loops with higher-order functions
In imperative programming languages is common to use for loops for iterating over data and
transforming it, usually the intent being one of the following:
-
Transform every value in the iterable yielding another iterable
-
Filter the elements of the iterable by a certain criteria
-
Convert the iterable to a value where each iteration depends on the result from the previous one
-
Run a computation for every value in the iterable
The above actions are encoded in higher-order functions and syntactic constructs in ClojureScript, let’s see an example of the first three.
For transforming every value in a iterable data structure we use the map function, which takes a
function and a sequence and applies the function to every element:
(map inc [0 1 2])
;; => (1 2 3)
For filtering the values of a data structure we use the filter function, which takes a predicate
and a sequence and gives a new sequence with only the elements that returned true for the given
predicate:
(filter odd? [1 2 3 4])
;; => (1 3)
Converting an iterable to a value accumulating the intermediate result in every step of the iteration
can be achieved with reduce, which takes a function for accumulating values, an optional initial value
and a collection:
(reduce + 0 [1 2 3 4])
;; => 10
for sequence comprehensions
In ClojureScript the for construct isn’t used for iteration but for generating sequences, an operation
also known as "sequence comprehension". It offers a small domain specific language for declaratively
building lazy sequences.
It takes a vector of bindings and a expression and generates a sequence of the result of evaluating the expression, let’s take a look at an example:
(for [x [1 2 3]]
[x x])
;; => ([1 1] [2 2] [3 3])
It supports multiple bindings, which will cause the collections to be iterated in a nested fashion, much
like nesting for loops in imperative languages:
(for [x [1 2 3]
y [4 5]]
[x y])
;; => ([1 4] [1 5] [2 4] [2 5] [3 4] [3 5])
We can also follow the bindings with three modifiers: :let for creating local bindings, :while for
breaking out of the sequence generation and :when for filtering out values.
Here’s an example of local bindings using the :let modifier, note that the bindings defined with it
will be available in the expression:
(for [x [1 2 3]
y [4 5]
:let [z (+ x y)]]
z)
;; => (5 6 6 7 7 8)
We can use the :while modifier for expressing a condition that, when it is no longer met, will stop
the sequence generation. Here’s an example:
(for [x [1 2 3]
y [4 5]
:while (= y 4)]
[x y])
;; => ([1 4] [2 4] [3 4])
For filtering out generated values we use the :when modifier like in the following example:
(for [x [1 2 3]
y [4 5]
:when (= (+ x y) 6)]
[x y])
;; => ([1 5] [2 4])
We can combine the modifiers shown above for expressing complex sequence generations or more clearly expressing the intent of our comprehension:
(for [x [1 2 3]
y [4 5]
:let [z (+ x y)]
:when (= z 6)]
[x y])
;; => ([1 5] [2 4])
When we outlined the most common usages of the for construct in imperative programming languages
we mentioned that sometimes we want to run a computation for every value in a sequence, not caring
about the result. Presumably we do this for achieving some sort of side-effect with the values of
the sequence.
ClojureScript provides the doseq construct, which is analogous to for but executes the expression
discarding the resulting values and returns nil.
(doseq [x [1 2 3]
y [4 5]
:let [z (+ x y)]]
(println x "+" y "=" z))
;; 1 + 4 = 5
;; 1 + 5 = 6
;; 2 + 4 = 6
;; 2 + 5 = 7
;; 3 + 4 = 7
;; 3 + 5 = 8
;; => nil
2.7. Collection types
2.7.1. Immutable and persistent
We mentioned before that ClojureScript collections are persistent and immutable but didn’t explain what we meant.
An immutable data structure, as its name suggest, is a data structure that can not be changed. In-place updates are not allowed in immutable data structures.
A persistent data structure is a data structure that returns a new version of itself when transforming it, leaving the original unmodified. ClojureScript makes this memory and time efficient using an implementation technique called structural sharing, where most of the data shared between two versions of a value is shared and transformations of a value are implemented by copying the minimal amount of data required.
Let’s see an example of appending values to a vector using the conj (for "conjoin") operation:
(let [xs [1 2 3]
ys (conj xs 4)]
(println "xs:" xs)
(println "ys:" ys))
;; xs: [1 2 3]
;; ys: [1 2 3 4]
;; => nil
As you can see, we derived a new version of the xs vector appending an element to it and got a new
vector ys with the element added.
For illustrating the structural sharing of ClojureScript data structures, let’s compare whether some parts
of the old and new versions of a data structure are actually the same object with the identical? predicate.
We’ll use the list data type for this purpose:
(let [xs (list 1 2 3)
ys (cons 0 xs)]
(println "xs:" xs)
(println "ys:" ys)
(println "(rest ys):" (rest ys))
(identical? xs (rest ys)))
;; xs: (1 2 3)
;; ys: (0 1 2 3)
;; (rest ys): (1 2 3)
;; => true
As you can see in the example, we used cons (construct) to prepend a value to the xs list and we got
a new list ys with the element added. The rest of the ys list (all the values but the first)
are the same object in memory that the xs list, thus xs and ys share structure.
2.7.2. The sequence abstraction
One of the central ClojureScript abstractions is the Sequence, which can be though as a list and can be derived from any of the collection types. It is persistent and immutable like all collection types and many of the core ClojureScript functions return sequences.
The types that can be used to generate a sequence are called "seqables", we can call seq on them and get
a sequence back. Sequences support two basic operations: first and rest. They both call seq on the
argument we provide them:
(first [1 2 3])
;; => 1
(rest [1 2 3])
;; => (2 3)
Calling seq on a seqable can yield different results if the seqable is empty or not, it will return nil
when empty and a sequence otherwise:
(seq [])
;; => nil
(seq [1 2 3])
;; => (1 2 3)
next is a similar sequence operation to rest, but it differs from the latter in that it yields a nil value
when called with a sequence with one or zero elements. Note that, when given one of the aforementioned sequences,
the empty sequence returned by rest will evaluate as a boolean true whereas the nil value returned by next
will evaluate as false:
(rest [])
;; => ()
(next [])
;; => nil
(rest [1 2 3])
;; => (2 3)
(next [1 2 3])
;; => (2 3)
nil-punning
The above behaviour of seq coupled with the falsey nature of nil in boolean contexts make an idiom for checking
the emptyness of a sequence in ClojureScript, which is often referred to as nil-punning.
(defn print-coll
[coll]
(when (seq coll)
(println "Saw " (first coll))
(recur (rest coll))))
(print-coll [1 2 3])
;; Saw 1
;; Saw 2
;; Saw 3
;; => nil
(print-coll #{1 2 3})
;; Saw 1
;; Saw 3
;; Saw 2
;; => nil
nil is also both a seqable and a sequence, and thus it supports all the functions we saw so far:
(seq nil)
;; => nil
(first nil)
;; => nil
(rest nil)
;; => ()
Functions that work on sequences
The ClojureScript core functions that work on collections call seq on their arguments, thus being
implemented in terms of generic sequence operations. This also makes them short-circuit when encountering empty
collections and being nil-safe.
We already saw examples with the usual suspects like map, filter and reduce but ClojureScript offers a
plethora of generic sequence operations in its core namespace. Note that many of the operations we’ll learn about
either work with seqables or are extensible to user defined types.
We can query a value to know wheter it’s a collection type with the coll? predicate:
(coll? nil)
;; => false
(coll? [1 2 3])
;; => true
(coll? {:language "ClojureScript" :file-extension "cljs"})
;; => true
(coll? "ClojureScript")
;; => false
Similar predicates exist for checking if a value is sequence (seq?) or a seqable (seqable?):
(seq? nil)
;; => false
(seqable? nil)
;; => false
(seq? [])
;; => false
(seqable? [])
;; => true
(seq? #{1 2 3})
;; => false
(seqable? #{1 2 3})
;; => true
(seq? "ClojureScript")
;; => false
(seqable? "ClojureScript")
;; => false
For collections that can be counted in constant time, we can use the count operation:
(count nil)
;; => 0
(count [1 2 3])
;; => 3
(count {:language "ClojureScript" :file-extension "cljs"})
;; => 2
(count "ClojureScript")
;; => 13
We can also get an empty variant of a given collection with the empty function:
(empty nil)
;; => nil
(empty [1 2 3])
;; => []
(empty #{1 2 3})
;; => #{}
The empty? predicate returns true if the given collection is empty:
(empty? nil)
;; => true
(empty? [])
;; => true
(empty? #{1 2 3})
;; => false
The conj operation adds elements to collections and may add them in different "places" depending
on the collection. It adds them where it makes more sense for the given collection performance-wise,
but note that not every collection has a defined order.
We can pass as many elements we want to add to conj, let’s see it in action:
(conj nil 42)
;; => (42)
(conj [1 2] 3)
;; => [1 2 3]
(conj [1 2] 3 4 5)
;; => [1 2 3 4 5]
(conj '(1 2) 0)
;; => (0 1 2)
(conj #{1 2 3} 4)
;; => #{1 3 2 4}
(conj {:language "ClojureScript"} [:file-extension "cljs"])
;; => {:language "ClojureScript", :file-extension "cljs"}
Lazyness
Most of ClojureScript sequence-returning functions generate lazy sequences instead of eagerly creating a whole new sequence. Lazy sequences generate their contents as they are requested, usually when iterating over them. Lazyness ensures that we don’t do more work that we need to and gives us the possibility to treat potentially infinite sequence as regular ones.
TODO
2.7.3. Collections in depth
Now that we’re acquainted with ClojureScript’s sequence abstraction and some of the generic sequence manipulating functions it’s time to dive into the concrete collection types and the operations they support.
Lists
In ClojureScript lists are mostly used as a data structure for grouping symbols together into programs. Unlike in other lisps, many of the syntactic constructs of ClojureScript use data structures different from the list (vectors and maps). This makes code less uniform but the gains in readability are well worth the price.
You can think of ClojureScript lists as singly linked lists, where each node contains a value and a pointer to the rest of the list.
This makes natural (and fast!) to add items to the front of the list since adding to the end would require to traverse the entire
list. The prepend operation is performed using the cons (construct) function.
(cons 0 (cons 1 (cons 2 ())))
;; => (0 1 2)
We used the literal () to represent the empty list. Since it doesn’t contain any symbol is not treated
as a function call. However, when using list literals that contain elements we need to quote them to
prevent ClojureScript from evaluating them as a function call:
(cons 0 '(1 2))
;; => (0 1 2)
Since the head is the position that has constant time addition in the list collection, the conj operation
on lists naturally adds item in the front:
(conj '(1 2) 0)
;; => (0 1 2)
Lists and other ClojureScript data structures can be used as stacks using the peek, pop and conj functions.
Note that the top of the stack will be the "place" where conj adds elements to, making conj equivalent to the
stack’s push operation. In the case of lists, conj adds element to the front of the list, peek returns the first
element of the list and pop returns a list with all the elements but the first one.
Note that the two operations that return a stack (conj and pop) don’t change the type of the collection used for
the stack.
(def list-stack '(0 1 2))
(peek list-stack)
;; => 0
(pop list-stack)
;; => (1 2)
(type (pop list-stack))
;; => cljs.core/List
(conj list-stack -1)
;; => (-1 0 1 2)
(type (conj list-stack -1))
;; => cljs.core/List
One thing that lists are not particularly good at is random indexed access. Since they are stored in a single linked list like structure in memory, random access to a given index requires a linear traversal in order to either retrieve the requested item or throw an index our of bounds error. Non-indexed ordered collections like lazy sequences also suffer from this limitation.
Vectors
Vectors are one of the most common data structures in ClojureScript. They are used as a syntactic construct in many
places where more traditional lisps use lists, for example in function argument declarations and let bindings.
ClojureScript vectors have enclosing brackets [] in their syntax literals, they can be created with vector and from
another collection with vec:
(vector? [0 1 2])
;; => true
(vector 0 1 2)
;; => [0 1 2]
(vec '(0 1 2))
;; => [0 1 2]
Vectors are, like lists, ordered collections of heterogeneous values. Unlike lists, vectors grow naturally from the tail
so the conj operation appends items to the rear of a vector. Insertion on the end of a vector is effectively constant
time:
(conj [0 1] 2)
;; => [0 1 2]
Another thing that differentiates lists and vectors is that vectors are indexed collections and as such support efficient
random index access and non-destructive updates. We can use the familiar nth function to retrieve values given an index:
(nth [0 1 2] 0)
;; => 0
Since vectors associate sequential numeric keys (indexes) to values we can treat them as an associative data structure. ClojureScript
provides the assoc function that, given an associative data structure and a set of key-value pairs, yields a new data structure with
the values corresponding to the keys modified.
(assoc [0 1 1] 2 2)
;; => [0 1 2]
Note that we can only assoc to a key that is either contained in the vector already or if it’s the last position in a vector:
(assoc [0 1 2] 3 3)
;; => [0 1 2 3]
(assoc [0 1 2] 4 4)
;; Error: Index 4 out of bounds [0,3]
Like with lists, vectors can be also used as stack with the peek, pop and conj functions. Note, however, that vectors grow
from the opposite end of the collection as lists:
(def vector-stack [0 1 2])
(peek vector-stack)
;; => 2
(pop vector-stack)
;; => [0 1]
(type (pop vector-stack))
;; => cljs.core/PersistentVector
(conj vector-stack 3)
;; => [0 1 2 3]
(type (conj vector-stack 3))
;; => cljs.core/PersistentVector
Since map and filter return lazy sequences but is common to need a fully realized sequence after performing those operations,
vector-returning counterparts of such functions are available as mapv and filterv. They have the advantage of being faster
than building a vector from a lazy sequence and making your intent more explicit:
(map inc [0 1 2])
;; => (1 2 3)
(type (map inc [0 1 2]))
;; => cljs.core/LazySeq
(mapv inc [0 1 2])
;; => [1 2 3]
(type (mapv inc [0 1 2]))
;; => cljs.core/PersistentVector
2.9. Namespaces
2.9.1. Defining a namespace
Namespaces is a clojurescript’s fundamental unit of code modularity. Are analogous to Java packages or
Ruby and Python modules, and can be defined with ns macro. Maybe if you are touched a little bit of
clojurescript source you have seen something like this at begining of the file:
(ns myapp.core
"Some docstring for the namespace.")
(def x "hello")
Namespaces are dynamic and you can create one in any time, but the convention is having one namespace per file. So, the namespace definition usually is at begining of the file followed with optional docstring.
Previously we have explained the vars and symbols. Every var that you are defines will be associated with one namespace. If you do not define a concrete namespace, the default one called "user" will be used:
(def x "hello")
;; => #'user/x
2.9.2. Loading other namespaces
It’s ok, definining a namespace and vars in it is really easy, but it is not very usefull if we can’t
use them from other namespaces. For this purpose, the ns macro also offers a simple way to load other
namespaces.
Observe the following:
(ns myapp.main
(:require myapp.core
clojure.string))
(clojure.string/upper-case myapp.core/x)
;; => "HELLO"
As you can observe, we are using fully qualified names (namespace + var name) for access to vars and functions from different namespaces.
It is ok, we not can access to other namespaces but is very boring always write the complete namespace
name for access to its vars and functions. It will be specially uncomfortable if a namespace name
is very large. For solve that, you can use the :as directive for create an additional (usually
more shorter) alias to the namespace. Let see the how it can be done:
(ns myapp.main
(:require [myapp.core :as core]
[clojure.string :as str]))
(str/upper-case core/x)
;; => "HELLO"
Additionaly, ClojureScript offers a simple way to refer specific vars or functions from concrete namespace using the :refer directive.
The :refer directive has two possible arguments: :all keyword or a vector of symbols that will
refer to vars in the namespace. With :all we are indicating that we want refer all public vars from the
namespace and with vector we can specify the concrete subset of vars that we want.
(ns myapp.main
(:require [myapp.core :refer :all]
[clojure.string :refer [upper-case]]))
And finally, we should know that everything that located in the cljs.core namespace is automatically
loaded and you should not require it explicitly. But sometimes you want declare vars that will clash
with some other defined in cljs.core namespace. For it, the ns macro offers an other directive that
allows exclude concrete symbols and prevet them to be automaticaly loaded.
Observe the following:
(ns myapp.main
(:refer-clojure :exclude [min]))
(defn min
[x y]
(if (> x y)
y
x))
The ns macro also has other directives for loading host clases (:import) and macros
(:refer-macros), but them are explained in posterior sections.
2.10. Abstractions and Polymorphism
I’m sure that in more that in one time you have found in this situation: you have defined a great abstraction (using interfaces or something similar) for your "bussines logic" and you have found the need to deal with an other module over which you have absolutelly no control, and you probably was thinking in create adapters, proxies and other approaches that will implies a great amount of additional complexity.
Some dynamic languages allows "monkey-patching", languages where the classes are open and any method can be defined and redefined at any time. Also, is very known that this technique is a very bad practice.
We can not trust languages that allows that when importing third party libraries, can silently overwrite methods that you are using and expecting a concrete behavior.
This symptoms denotes a commonly named: "Expression problem".
TODO: add link to expression problem description
2.10.1. Protocols
The ClojureScript primitive for define "interfaces" are called Protocols. A protocol consists in
a name and set of functions. All functions have at least one argument corresponding to the
this in javascript or self in Python.
Protocols provides a type based polymorphism, and the dispatch is always done by the
first argument previously mentioned as this.
A protocol looks like this:
(ns myapp.foobar)
(defprotocol IProtocolName
"A docstring describing the protocol."
(sample-method [this] "A doc string of the function associated with the protocol."))
|
Note
|
the "I" prefix is very common for make clear separation of protocols and types. In clojute comunity it there many dispare optionions about the use of the "I" prefix. In our opinion is an acceptable solution for avoid name clashing and confusions. |
From the user perspective, protocol functions are simple and plain functions defined in the namespace where the protocol is defined. As you can intuit, this makes protocols completelly namespaces and avoid any accidental clashing between implemented protocols for same type.
Extending to existing types
On of the big strengths of protocols is the ability to extend existing and maybe third party types and this operation can be done in different ways. The majority of time you will be tend to use the extend-protocol or the extend-type macros.
This is the aspect on how extend-type macro can be used:
(extend-type TypeA
ProtocolA
(function-from-protocol-a [this]
;; implementation here
)
ProtocolB
(function-from-protocol-b-1 [this parameter1]
;; implementation here
)
(function-from-protocol-b-2 [this parameter1 parameter2]
;; implementation here
))
You can observe that with extend-type you are extending one type with different protocols in one expression. In difference to that, extend-protocol do just the inverse operation. It, given a protocol, add implementation for it to multiple types:
(extend-protocol ProtocolA
TypeA
(function-from-protocol-a [this]
;; implementation here
)
TypeB
(function-from-protocol-a [this]
;; implementation here
))
It there other ways to extend a type with a protocol implementation but them will be covered in other section of this book.
Participate in ClojureScript abstractions
ClojureScript it self is built up on abstractions defined as protocols, so almost all behavior in the ClojureScript language can be adopted for third party libraries. Let’s go to see an real life example.
In previous sections we have explained different kind of builtin collections, in this case we will use the Set's. See this snipped of code:
(def mynums #{1 2})
(filter mynums [1 2 4 5 1 3 4 5])
;; => (1 2 1)
But, that it happens where? In this case, the set type implements the ClojureScript internal
IFn protocol that represents an abstraction for functions or any thing callable. So it can be
used like a callable predicate in filter.
Ok, but what it happens if we want use a regular expression as predicate function for filter a collection of strings:
(filter #"^foo" ["haha" "foobar" "baz" "foobaz"])
;; TypeError: Cannot call undefined
Obviosly, this exception is raised because the RegExp type does not implements the IFn protocol
so it can not behave like a callable. But it can be easy fixed:
(extend-type js/RegExp
IFn
(-invoke
([this a]
(re-find this a))))
Now, you will be able use the regex instances as predicates in filter operation:
(filter #"^foo" ["haha" "foobar" "baz" "foobaz"])
;; => ("foobar" "foobaz")
Protocols introspection
ClojureScript comes with a usefull function that allows runtime introspection: satisfies?. The
purpose of this function is know in runtime if some object (instance of some type) satisfies the
concrete protocol.
So, with previous examples, if we check if a set instance satisfies a IFn protocol, it should
return true:
(satisfies IFn #{1})
;; => true
2.10.2. Multimethods
We have previously talked about protocols, that solves a very common use case of polymorphism: dispatch by type. But in some circumstances the protocol’s approach it can be limiting. And here multimethods comes to the rescue.
The multimethods are not limited to type dispatch only, instead, them also offers dispatch by types of multiple arguments, by value and allows ad-hoc hierarchies to be defined. Also, like protocols, is a "Open System" so you or any third parties can extend a multimethod for new types.
The basic consturctions of multimethods consists in defmulti and defmethod forms. The
defmulti form is used for create the multimethod with initial dispatch function. This is
a common look and feel of it:
(defmulti say-hello
"A polymorphic function that return a greetings message
depending on the language key with default lang as `:en`"
(fn [param] (:locale param))
:default :en)
The anonymous function defined within the defmulti form is a dispatch function. It will
be called in every call to say-hello function and should return some kind of mark object
that will be used for dispatch. In our example it returns the contents of :locale key
of the first argument.
And finally, we should add implementations. That is done with defmethod form:
(defmethod say-hello :en
[person]
(str "Hello " (:name person "Anonymous")))
(defmethod say-hello :es
[person]
(str "Hola " (:name person "Anonimo")))
So, if you execute that function over a hash map containing the :locale and optionally
the :name key, the multimethod firstly will call the dispatch function for determine the
dispatch value, secondly it will search an implementation for that value, if it is found,
it will execute it, in case contrary it will search the default implementation (if it
specified) and execute it.
(say-hello {:locale :es})
;; => "Hola Anonymo"
(say-hello {:locale :en :name "Ciri"})
;; => "Hello Ciri"
(say-hello {:locale :fr})
;; => "Hello Anonymous"
If the default implementation is not specified, an exception will be raised notifying about that some value does not have a implementation for that multimethod.
2.10.3. Hierarchies
Hierarchies is a way that ClojureScript offers you build a whatever relations that your domain may require. The hierarchies are difined in term of relations betwen named objects, such as symbols, keywords or types.
The hierarchies can be defined globally or locally, depending on your needs. Like multimethods, hierarchies are not limited to single namespace. You can extend a hierarchy from any namespace, not necesary the one which they are defined.
The global namespace is more limited, for good reasons. Not namespaced keywords or symbols can not be used in the global hierarcy. That behavior helps prevent unexpected situations when two or more third party libraries uses the same symbol for different semantics.
Defining a hierarchy
The hierarchy relations should be established using derive function:
(derive ::circle ::shape)
(derive ::box ::shape)
We have just defined a set of relationships between namespaced keywords, in this case the
::circle is a child of ::shape and ::box is also a child of ::shape.
|
Tip
|
The ::circle keyword syntax is a shortland for :current.ns/circle. So if you are executing
it in a repl, sureally that ::circle will be evaluated to :cljs.user/circe.
|
Hierarchies introspection
ClojureScript comes with little toolset of functions that allow runtime introspection
of the global or local defined hierarchies. These toolset consists on thre functions:
isa?, anscestors, and descendants.
Let see an example on how it can be used with hierarchy defined in previous example:
(ancestors ::box)
;; => #{:cljs.user/shape}
(descendants ::shape)
;; => #{:cljs.user/circle :cljs.user/box}
(isa? ::box ::shape)
;; => true
(isa? ::rect ::shape)
;; => false
Local defined hierarchies
As we mentioned previously, in ClojureScript you also can define local hierarchies. This can be
done with make-hierarchy function. And this is the aspect of how you can replicate the previous
example but using the local hierarchy:
(def h (-> (make-hierarchy)
(derive :box :shape)
(derive :circle :shape)))
Now, if you can use the same introspection functions with that, locally defined hierarchy:
(isa? h :box :shape)
;; => true
(isa? :box :shape)
;; => false
As you can observe, in local hierarchies we can use normal (not namespace qualified) keywords
and if we execute the isa? without passing the local hierarchy parameter, its as expected
return false.
Hierarchies in multimethods
One of the big advantages of hierarchies, is that they works very well together with multimethods.
Because, multimethods by default uses the isa? function for the last step of dispatching.
Let see an example for clearly understand that it means. Firstly define the multimethod with
defmulti form:
(defmulti stringify-shape
"A function that prints a human readable representation
of a shape keyword."
identity
:hierarchy h)
With :hierarchy keyword parameter we indicate to the multimethod that hierarchy we want to use,
if it is not specified, the global hierarchi will be used.
Secondly, define a implementation for our multimethod using the defmethod form:
(defmethod stringify-shape :box
[_]
"A box shape")
(defmethod stringify-shape :shape
[_]
"A generic shape")
(defmethod stringify-shape :default
[_]
"Unexpected object")
Now, let see what is happens if we execute that function with a box:
(stringify-shape :box)
;; => "A box shape"
Now everything works as expected, the multimethod executes the direct matching implementation
for the given parameter. But that is happens if we execute the same function but with :circle
keyword as parameter, that does not have the direct matching dispatch value:
(stringify-shape :circle)
;; => "A generic shape"
The multimethod automatically resolves it using the provided hierarchy, and that :circle is
a descendat of :shape, so the :shape implementation is executed.
2.11. Data types
Until, now, we have used maps, sets, lists and vectors for represent our data. And in most cases is a really great aproach for do it. But some times we need define our own types and in this book we will call them datatypes.
A datatype provides the following:
-
A unique host backed type, either named or anonymous.
-
Explicitly declared structure using fields or closures.
-
Implement concrete abstractions.
-
Map like behavior (via records, see below).
2.11.1. Deftype
The most low level construction in ClojureScript for create own types, is the deftype macro. For
demostration we will define a type called User:
(deftype User [firstname lastname])
Once the type has beed defined, we can create an instance of our User:
(def user (User. "Triss" "Merigold"))
And its fields can be accesset using the prefix-dot notation:
(.-firstname user)
;; => "Triss"
Types defined with deftype (and posteriory with defrecord) creates a host backed class like object
associated to the current namespace. But it has some peculiarities when we intend to use or import
it from other namespace. The types in ClojureScript should be imported with :import directive
of ns macro:
(ns myns.core
(:import otherns.User))
(User. "Cirilla" "Fiona")
For convenience, ClojureScript also defines a constructor function caled →User that can be imported
with the common way using :require directive.
We personally do not like this type of functions, and we prefer define own constructors, with more idiomatic names:
(defn user
[firstname lastname]
(User. firstname lastname))
And use it in our code instead of →User.
2.11.2. Defrecord
The record is a slightly higher level abstraction for define types in ClojureScript and should be prefered way to do it.
As we know, ClojureScript tends to use plain data types how are the maps but in most cases we need have a named type for represent the entities of our application. Here come the records.
A record is a datatype that implements a map protocols and therefore can be used like any other map. And since records are also proper types, they support type-based polymorphism through protocols.
In summary: with records, we have the best of both worlds, maps that can play in in different abstractions.
Let start defining the User type but using records:
(defrecord User [firstname lastname])
It looks really similar to deftype syntax, in fact, it uses deftype behind the scenes as low level primitive for defining types.
Now, look the difference with raw types for access to its fields:
(def user (User. "Yennefer" "of Vengerberg"))
(:username user)
;; => "Yennefer"
(get user :username)
;; => "Yennefer"
As we mention previously, records are maps and acts like tham:
(map? user)
;; => true
And like maps, tham support extra fields that are not initially defined:
(def user2 (assoc user :age 92))
(:age user2)
;; => 92
As we can see, the assoc function works as is expected and return a new instance of the same
type but with new key value pair. But take care with dissoc, its behavior with records is slightly
different that with maps; it will return a new record if the field being dissociated is an optional
field, but it will return a plain map if you dissociate the mandatory field.
An other difference with maps is that records does not acts like functions:
(def plain-user {:username "Yennefer", :lastname "of Vengerberg"})
(plain-user :username)
;; => "Yennefer"
(user :username)
;; => user.User does not implements IFn protocol.
The defrecord macro like the deftype, for convenience esposes →User function, but with additional
one map→User constructor function. We have the same opionon about that constructors that with
deftype defined ones: we recommend define own instead of use that ones. But as they exists, let see
how they can be used:
(def cirilla (->User "Cirilla" "Fiona"))
(def yen (map->User {:firstname "Yennefer"
:lastname "of Vengerberg"}))
2.11.3. Implement protocols
Both type definition primitives that we have seen until now allows inline implementations for protocols (explained in previous section). Let start define one for example purposes:
(defprotocol IUser
"A common abstraction for work with user types."
(full-name [_] "Get the full name of the user."))
Now, you can define a type with inline implementation for an abstraction, in our case the IUser:
(defrecord User [firstname lastname]
IUser
(full-name [_]
(str firstname " " lastname)))
;; Create an instance.
(def user (User. "Yennefer" "of Vengerberg"))
(full-name user)
;; => "Yennefer of Vengerberg"
2.11.4. Reify
The reify macro lets you create an anonymous types that implement protocols. In difference with
deftype and defrecord, it does not has accessible fields.
This is a way how we can emulate an instance of user type and that plays well in IUser abstraction:
(defn user
[firstname lastname]
(reify
IUser
(full-name [_]
(str firstname " " lastname))))
(def yen (user "Yennefer" "of Vengerberg"))
(full-name user)
;; => "Yennefer of Vengerberg"
The real purpose of reify is create anonymous types that plains in a concrete abstractions but you do not want a type in self.
2.12. Host interoperability
ClojureScript in the same way as it brother Clojure, is designed to be a "Guest" language. It means that the design of the language fits very well to work on to of existing ecosystem such as javascript for ClojureScript and jvm for Clojure.
2.12.1. The types.
ClojureScript unlike expected, try takes advantage of every type that the platform provides. This is a maybe incomplete list of things that ClojureScript inherits and reuse from the underlying platform:
-
ClojureScript strings are javascript Strings.
-
ClojureScript numbers are javascript Numbers.
-
ClojureScript
nilis a javascript null. -
ClojureScript regular expressions are javascript
RegExpinstances. -
ClojureScript is not interpreted, is always compiled town to the javascript.
-
ClojureScript allows easy call platform apis with the same semantics.
-
ClojureScript data types internally compiles to objects in javascript.
On top of it, ClojureScript buid own abstractions and types that are does not exists in the platform, such as Vectors, Maps, Sets, and others that are explained in previous chapters.
2.12.2. Interacting with platform types
ClojureScript comes with a little set of special forms that allows interact with platform types such as calling object methods, creating new instances and accessing to object properties.
Access to the platform
ClojureScript has a special syntax for access to the all platform environment through the
js/ special namespace. This is the aspect of the expression for execute the javascript’s
builtin parseInt function:
(js/parseInt "222")
;; => 222
Creating new instances
ClojureScript has two ways to create instances:
new special form(new js/Regex "^foo$")
Using the . special form
(js/Regex. "^foo$")
The last one is the recommended way to do that operation. We do not aware of real differences between the two forms, but in the clojurescript comunity the last one is the most adopted.
Invoke instance methods
For invoke methods of some object instance, in contrary to how it used in javascript (eg:
obj.method(), the method name comes first like any other standard function in lisp languages
but with little variation: the function name starts with special form ..
Let see how we can call the .test() method of regexp instance:
(def re (js/RegExp "^foo"))
(.test re "foobar")
;; => true
Access to object properties
Access to the object properties is really very similar to call a method, the difference is that
instead of using the . we should use the .-. Let see an example:
(.-multiline re)
;; => false
Javascrpt objects
ClojureScript has different ways for create plain javascript objects, each one has its own
purpose. The basic one is the js-obj function. It accepts a variable length of pairs of key
values and return a javascript object:
(js-obj "foo" "bar")
;; => #js {:foo "bar"}
The return value can be passed to some kind of third party library that accepts a plain javascript objects. But you can observe the repl representation of the return value of this function. It is exactly the other form for do the same thing.
Using the reader macro #js consists of prepend it to the clojure map or vector and the
result will be transformed to plain javascript:
(def myobj #js {:foo "bar"})
The translation of that to plain javascript is similar to this:
var myobj = {foo: "bar"};
As explained in previous section, you also can access to the plain object properties using
the .- syntax:
(.-foo myobj)
;; => "bar"
And as javascript objects are mutable, you can set a new value to some property using
the set! function:
(set! (.-foo myobj) "baz")
Conversions
The inconvenience of previously explained forms, is that they does not make recursive
transformatios, so if you have nested objects, the nested objects do not will be converted.
For solve that use cases, ClojureScript comes with clj→js and js→clj functions
that transforms clojure collection types into javascript and in reverse order:
(clj->js {:foo {:bar "baz"}})
;; => #js {:foo #js {:bar "baz"}}
In case of arrays, it there a specialized function into-array that behaves as it expected:
(into-array ["foo"])
;; => #js ["foo"]
Arrays
In previous example we have seen how we can create an array from existing ClojureScript
collection. But it there other function for create arrays: make-array.
(def a (make-array 10))
;; => #js [nil nil nil nil nil nil nil nil nil nil]
In ClojureScript arrays are also play well in sequence abstraction so you can iterate
over it or simple get the number of elements with count function:
(count a)
;; => 10
As arrays are platform mutable collection type, you can acces to a concrete index and set value to on that position:
(aset a 0 2)
;; => 2
Or access in a indexed way to it values:
(aget a 0)
;; => 2
In javascript, the objects are also arrays, so you can use the same functions for interacting with plain objects:
(def b #js {:foo "bar"})
;; => #js {:foo "bar"}
(aget b "foo")
;; => "bar"
(aset b "baz" "bar")
;; => "bar"
b
;; => #js {:foo "bar", :baz "bar"}
2.14. Truthiness
This is the aspect where the each language has its own semantics, the majority of languages
treats empty collections, the 0 integer and other things like this are considered false.
In ClojureScript unlike in other languages only two values are considered as false: nil
and false, Everything except them, are treated as true.
So, thanks to it, sets can be considered also predicates, so if them return a value so it exists
and if it return nil so the value does not exists:
(def s #{1 2})
(s 1)
;; => 1
(s 3)
;; => nil
3. 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 each other.
3.1. Getting Started with ClojureScript compiler
At this point, you will surely be very bored with the constant theoric explanations about the language itself and will want to write and execute some code. The goal of this section is to provide a little practical introduction to the ClojureScript compiler.
The ClojureScript compiler takes the source code that has been split over numerous directories and namespaces and compiles it down to javascript. Today, javascript has great amount of different environments where it can be executed - each with their own pecularities.
This chapter intends to explain how to use ClojureScript without any additional tooling. This will help you understand how the compiler works and how you can use it when other tooling is not available (such as leiningen+cljsbuld or boot).
3.1.1. Execution environments
What is an execution environment? An execution environment is an engine where javascript can be executed. For example, the most popular execution environment is a Browser (Chrome, Firefox, …) followed by the second most popular - nodejs/iojs.
There are others, such as Rhino (jdk6+), Nashorn (jdk8), 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/iojs like environments out of the box.
3.1.2. Download the compiler
The ClojureScript compiler is implemented in java, and to use it, you should have jdk8 installed. ClojureScript itself only requires jdk7, but the standalone compiler that we going to use in this chapter requires jdk8.
You can download it using wget:
wget https://github.com/clojure/clojurescript/releases/download/r3211/cljs.jar
The ClojureScript compiler is packages in a standalone executable jar file, so this is the unique file and jdk8 that you need for compile the ClojureScript source code to javascript.
3.1.3. Compile for nodejs/iojs
Let start with a practical example compiling code that will target nodejs/iojs. For it you should have installed nodejs or iojs (recommended).
There are different ways to install iojs, but the most recommended way is using nvm (node version manager). You can read the instructions to install and use nvm on:https://github.com/creationix/nvm[home page].
You can test iojs installed in your system with this command:
$ iojs --version
v1.7.1
Create the example application
For the first step of our practical example, we should create our application directory structure and populate it with example code.
So start creating the directory tree structure for our "hello world" application:
mkdir -p src/myapp
touch src/myapp/core.cljs
Second, write the example code into the previously created src/myapp/core.cljs file:
(ns myapp.core
(:require [cljs.nodejs :as nodejs]))
(nodejs/enable-util-print!)
(defn -main [& args]
(println "Hello world!"))
(set! *main-cli-fn* -main)
|
Note
|
Is very important that the declared namespace in the file exactly matches the directory structure. Is the way ClojureScript structures its source code. |
Compile the example application
In order to compile that source code, we need a simple build script that instructs the ClojureScript compiler with the source directory and the output file. ClojureScript has a lot of other options but at this moment we can ignore that.
Lets create the build.clj file with the following content:
(require 'cljs.closure)
(cljs.closure/build "src"
{:output-to "main.js"
:main 'myapp.core
:target :nodejs})
This is a breif explanation of the used compiler options:
-
The
:output-toparameter indicates to the compiler the destination of the compiled code, in this case to the "main.js" file. -
The
:mainproperty indicates to the compiler the namespace that will acts as the entry point of your application when it’s executed. -
The
:targetproperty indicates the platform where you want execute the compiled code. In this case we are going to use iojs (formerly nodejs). If you omit this parameter the source will be compiled to run in the browser environment.
To run the compilation, just execute the following command:
java -cp cljs.jar:src clojure.main build.clj
And when it finishes, execute the compiled file using iojs:
$ iojs main.js
"Hello world"
3.1.4. Compile for the Browser
In this section we are going to create a similar "hello world" example aplication from the previous section to run in the browser environent. The minimal requirement for it is just a browser that can execute javascript.
The process is almost the same, the directory structure is the same. The only things that changes is the entry point of the application and the build script.
Start writing new content to the src/myapp/core.cljs` file:
(ns myapp.core)
(enable-console-print!)
(println "Hello world!")
In the browser environment we do not need a specific entry point for the application, so the entry point is the entire namespace.
Compile the example application
In order to compile the source code to run properly in a browser, overwrite the build.clj file with the following content:
(require 'cljs.closure)
(cljs.closure/build "src"
{:output-to "main.js"
:output-dir "out/"
:source-map "main.js.map"
:main 'myapp.core
:optimizations :none})
This is a brief explanation of the used compiler options:
-
The
:output-toparameter indicates to the compiler the destination of the compiled code, in this case to the "main.js" file. -
The
:mainproperty indicates to the compiler the namespace that will acts as the entry point of your application when it’s executed. -
:source-mapindicates the destination of the source map. -
:output-dirindicates the destination directory for all files sources used in a compilation. Is just for make source maps works properly with the rest of code, not only your source. -
:optimizationsindicates the compilation optimization. There are different values for this option but that will be covered in following sections in more detail.
To run the compilation, just execute the following command:
java -cp cljs.jar:src clojure.main build.clj
This process can take some time, so do not worry, wait a little bit. The jvm bootstrap with Clojure compiler is slightly slow. In the following sections we will explain how to start a watch process to avoid constantly starting and stopping the slow process.
While waiting for the compilation, let’s create a dummy html file to make it easy to execute our example app in the browser. Create the index.html file with the following content:
<!DOCTYPE html>
<html>
<header>
<meta charset="utf-8" />
<title>Hello World from ClojureScript</title>
</header>
<body>
<script src="main.js"></script>
</body>
</html>
Now, when compilation is completed and you have the basic html file, just open it with your favorite browser and take a look in a development tools console. There should appear the "hello world" message.
3.1.5. Watch process
Surely, you have already experienced the slow startup of the ClojureScript compiler. To solve this, the ClojureScript standalone compiler also comes with tools to start a process to watch changes in some directory and perform an incremenetal compilation.
Start creating another build script, but in this case name it watch.clj:
(require 'cljs.closure)
(cljs.closure/watch "src"
{:output-to "main.js"
:output-dir "out/"
:source-map "main.js.map"
:main 'myapp.core
:optimizations :none})
Now, execute that script like any other that you have executed in previos sections:
$ java -cp cljs.jar:src clojure.main build.clj
Building ...
Reading analysis cache for jar:file:/home/niwi/cljsbook/playground/cljs.jar!/cljs/core.cljs
Compiling out/cljs/core.cljs
Using cached cljs.core out/cljs/core.cljs
... done. Elapsed 0.8354759 seconds
Watching paths: /home/niwi/cljsbook/playground/src
Change detected, recompiling ...
Compiling src/myapp/core.cljs
Compiling out/cljs/core.cljs
Using cached cljs.core out/cljs/core.cljs
... done. Elapsed 0.191963443 seconds
You can observe that in the second compilation, the time is drastically reduced. Another advantage of this method is that it is a little bit more verbose.
3.1.6. Optimization levels
The ClojureScript compiler has different level of optimizations. Behind the scenes, those compilation levels are coming from Google Closure Compiler.
A very inacurate overview of the compilation process is:
-
The reader reads the code and makes some analysis. This process can raise some warnings during its phase.
-
Then, the ClojureScript compiler emits javascript code. The result of that is one javascript file for each cljs file.
-
The generated files pases throught the Closure Compiler that depending on the optimization level, and other options (sourcemaps, output dir output to, …) generates the final output.
The final output dependends strictly on the optimization level.
none
Implies that closure compiler is noop, just writes the files as is, without any additional optimization applied to the source code. This optimization level is manadatory if you are targeting nodejs or iojs, and the appropiate in development mode when your code targets the browser.
whitespace
This optimization level consists of concatenating the compiled files in an appropriate order, removing line breaks and other whitespaces and generating the output as one unique file.
It also has some compilation speed penality, resulting in slower compilations. In any case it is not very very slow and is completely usable in small/medium applications.
simple
The simple compilation level implies (includes) all transformations from whitespace optimization and additionally performs optimizations within expressions and functions, including renaming local variables and function parameters to shorter names.
Compilation with :simple optimization always preserves the functionality of syntactically valid
JavaScript, so it does not interfere with the interaction between the compiled ClojureScript and
other JavaScript.
advanced
TBD
4. Mixed Bag
This chapter will cover miscellaneous topics that are not classified in the previous ones. This is a "catchall" section and will touch a bunch of heterogeneus topics like:
-
Async primitives using the core.async library.
-
Working with promises.
-
Error handling using the cats library.
-
Pattern matching with the core.match library.
-
Web development using the Om library.
-
Share code betwen clojure and clojurescript.