Chapter 4. ClojureScript Basics
ClojureScript is a simple language, which is to say that it is based on a small number of fundamental concepts. If you have only written programs in imperative, object-oriented languages such as Java, C++, and JavaScript, then some of these concepts may be unfamiliar to you at first. However, by learning those concepts, you will be rewarded with a powerful new programming tool.
ClojureScript versus Clojure
At the language level, ClojureScript is designed to mimic Clojure as much as possible. However, neither ClojureScript nor Clojure makes any attempt to hide operational details of the underlying host platform, JavaScript or the JVM, respectively. As a result, there will be differences between the two languages wherever their host platforms are involved:
Calls to host methods or classes
Built-in types such as strings and numbers
Built-in operations such as arithmetic
Concurrency and threading (JavaScript is single-threaded)
Performance
At this time, ClojureScript does not implement all of the Clojure language. In particular, ClojureScript does not include most of the concurrency features for which Clojure is so well known; because JavaScript VMs are single-threaded, these features are less important. There are also features of Clojure that have not yet been implemented in ClojureScript simply because work has not yet been completed.
Clojure itself is a young programming language (first released in 2007) but it has grown rapidly in stability, ease of use, and performance. ClojureScript is even younger (first released in 2011) and is consequentially less mature. You can expect to find rough edges, bugs, and undocumented features. While we hope that this book will help to ameliorate the latter, nothing can take the place of experience that comes from building real-world applications.
This book does not attempt to fully document all the features of the Clojure language, or even all of the features currently implemented in ClojureScript. Instead, we will attempt to provide enough to get you started and working productively in ClojureScript. When you are ready to learn more, there are many books available on the Clojure language: most of their material will apply equally well to ClojureScript.
Expressions and Side Effects
Most mainstream programming languages, including JavaScript, have
both statements and expressions.
In JavaScript, statements end with a semicolon (usually) and are typically
related to flow control: for
, if
,
while
, and so on. JavaScript expressions include literals
(numbers, strings, regexes), function calls, and arithmetic operations.
The key difference is that expressions always have a
value whereas statements do not. Expressions can be
nested: you can place a function call expression inside an if
statement, but not the other way around.
In ClojureScript, everything is an expression and everything has a
value, even the control structures. (Sometimes that value is
null
, but it’s still a value.) You can even define your
own flow-control expressions using macros, which we
will cover in Chapter 8. The process of going from an
expression to its value is called evaluating the
expression.
Some expressions can have side effects, things that happen when they are evaluated other than simply returning a value. Printing output to the screen or manipulating an HTML document in a web browser are both side effects. ClojureScript favors a “functional” style of programming in which most code consists of “pure” expressions that return a value with no side effects. Of course, a program entirely without side effects cannot produce any output at all, so ClojureScript allows you to break out of the functional style when you need to.
Syntax and Data Structures
As we said, everything in ClojureScript is an expression, including the primitive data types, which “evaluate” to themselves. Comments begin with a semicolon and continue to the end of a line.
42, 3.14159 ; Numbers "Hello, World!" ; String #"\d{3}-\d{3}-\d{4}" ; RegExp true, false ; Boolean nil ; null
ClojureScript numbers and strings are the same as JavaScript
Number
and String
objects, with essentially the
same syntax. ClojureScript regular expressions evaluate to JavaScript
RegExp
but have slightly different syntax.
Symbols and Keywords
map, +, swap! ; Symbols :meta, :my-id ; Keywords
ClojureScript has symbols, which are just
bare words in your program. Symbols evaluate to other values, such as
functions, and also serve a role similar to local variables, although
they are not really variables. The name of a symbol
can contain almost any character, including hyphens and other
punctuation. Things that are typically special operators in other
languages, such as the arithmetic operators +
,
-
, *
, and /
, are just symbols in
ClojureScript, which evaluate to the built-in arithmetic
functions.
ClojureScript also has keywords, written as symbols with a leading colon. Keywords always evaluate to themselves. Unlike symbols, they never stand in for anything else. In JavaScript, strings are often used for constants or identifiers in code; keywords fill the same role in ClojureScript.
Data Structures
(1 2 3), (print "Hello") ; Lists [:a :b :c 1 2 3] ; Vector {:a 1, "b" 2} ; Map #{3 7 :z} ; Set
Finally, there are the four basic data structures. Vectors, maps, and sets evaluate to themselves: they are literal data structures similar to JavaScript’s arrays and objects. Individual elements in a data structure must be separated by whitespace. In ClojureScript, commas count as whitespace in addition to the usual space, tab, and line break. We will talk more about these data structures in the next chapter.
Lists can be used as literal data, but more often they are used to construct expressions. When the ClojureScript compiler encounters a list, it examines the first element of the list and tries to invoke it. The first element is the function position of the list. It is usually a symbol naming a function, but it could also be a macro or special operator, which we will define later.
Even operators like +
and *
are
functions, so they must appear in function position. ClojureScript code
therefore uses prefix notation instead of the more
common algebraic notation used by most programming
languages. In ClojureScript, the parentheses are always required, but
there are no “operator precedence” rules to remember:
(+ 9 (* 10 5)) ; 9 + 10 * 5 in algebraic notation
As for syntax, that’s (almost) all there is to it! Everything in ClojureScript is composed from these simple parts.
The following expression contains two lists, one nested inside the other:
(println (+ 3 4))
The outer list contains two elements: the symbol
println
in function position and the inner list. The inner
list has the symbol +
in function position, followed by two
numbers.
Expressions are evaluated from the inside out, so this example
will compile into JavaScript code, which adds 3
to
4
and then prints the result. Printing is a side
effect; the whole expression evaluates to
nil
, which is the value returned by the
println
function. If you type this expression into the
ClojureScript REPL, you will see 7
printed on one line and
nil
on the following line.
Special Forms and Definitions
As we mentioned in the previous section, the symbol in the function
position of a list may be a function, macro, or special operator. Special
operators are symbols that are defined by the ClojureScript compiler.
These are the “primitives” of the language, and there are only a handful
you will encounter, such as if
, def
, and
do
. Most of the standard operators in ClojureScript, such as
arithmetic and control flow, are handled by functions and macros.
One special operator you will use often is def
, which
defines a new binding from a symbol to a value. After
a new binding is created with def
, evaluating the symbol will
return its value.
For example, here we bind the symbol my-name
to a
string:
(def my-name "Leslie Q. Coder")
Symbol bindings created with def
compile into
JavaScript var
declarations, but you should think of them as
constants, not local variables. In particular, def
expressions are not intended to be used inside functions, nor should they
be used to rebind symbols to new values (except during interactive
development at the REPL).
Functions
ClojureScript functions are very much like JavaScript functions. The
fn
macro creates unnamed, anonymous
functions, like JavaScript’s function
operator.
The fn
symbol appears in function position of a list,
followed by the parameters (arguments) to the
function as a vector of symbols, followed by one or more expressions
comprising the body of the function. Here is a simple
function:
(fn [name] (str "Hello, " name))
This function takes one argument, called name
, and
calls the str
function, which concatenates strings, in its
body. It compiles to JavaScript that looks something like this:
function(name) { return cljs.core.str("Hello, " name); }
A function isn’t very useful unless we can call it. Remember that
the first element of a list is evaluated as a function, so we can place a
literal fn
at the front of a list to invoke it. The arguments
we want to pass to the function are the remaining elements of the
list:
((fn [name] (str "Hello, " name)) "ClojureScript") ;;=> "Hello, ClojureScript"
This example compiles to JavaScript that creates an anonymous function and immediately invokes it, like this:
(function(name) { return cljs.core.str("Hello, " name); })("ClojureScript");
Even this is not very practical, so we usually want to give our
functions names. Functions are values like any other, so we can use the
def
macro to bind them to symbols. Here we bind the symbol
greeting
to a function and then call it by name:
(def greeting (fn [name] (str "Hello, " name))) (greeting "functions!") ;;=> "Hello, functions!"
Binding symbols to functions is so common that ClojureScript has a
macro to make it easier. The defn
macro takes a symbol to
define, followed by the parameter vector and function body as with
fn
.
(defn greeting [name] (str "Hello, " name))
We will explore macros further in Chapter 8. For now, just know that they can control the way things are evaluated.
Multi-Arity Functions
In JavaScript, any function can be called with any number of
arguments, and those arguments can be accessed via the
arguments
array. ClojureScript allows functions to be
defined with several arities, or numbers of
arguments. Each arity of the function can have different behavior. A
multi-arity function looks like this:
(defn greeting ([] (greeting "Hello" "world")) ([name] (greeting "Hello" name)) ([salutation name] (str salutation ", " name "!")))
Each arity of the function is its own list inside the function definition. The first element of each list is the argument vector, followed by the function body. This example demonstrates a common use of multi-arity functions: to provide default values for some or all of the parameters. Multi-arity functions may feel similar to “function overloading” in languages such as C and Java, with the difference that they are overloaded only on the number, not the type, of their arguments.
Variadic Functions
In addition to having multiple arities, a ClojureScript function
can be defined to take any number of arguments: this is called a
variadic function. A variadic function has the
special symbol &
(ampersand) before the last symbol in
its argument vector, as in the following:
(defn average ([x] x) ([x y] (/ (+ x y) 2)) ([x y & extra] (/ (reduce + (+ x y) extra) (+ 2 (count extra)))))
This example defines a function with three arities, the last of
which is variadic. If the average
function is called with
one argument, it returns that argument. If it is called with two
arguments, it adds them together and divides by 2. If it is called with
three or more arguments, it computes their average
using the reduce
and count
functions, which we
will cover in Chapter 6. Notice that a function can
be both multi-arity and variadic at the same time, but only one of a
function’s arities can be variadic.
Local Bindings
ClojureScript does not have variables like JavaScript because all
data is immutable, but it does permit you to create a local
binding between a symbol and a value with the let
expression, as shown below:
(let [binding-form value-expr
...]
... expressions ...)
The let
expression begins with a vector of
bindings. Each binding is a pair: first a
binding form, usually a symbol, then a
value expression. When evaluated, the
let
evaluates the value expressions, in order, and binds them
to the symbols in the binding forms. This creates a local binding within
the body of the let
. For example:
(let [x 4 y (+ x 3)] (println "The product of" x "and" y "is") (println (* x y))) ;; The product of 4 and 7 is ;; 28 ;;=> nil
Notice that the value expressions can include references to the symbols created in earlier bindings.
Destructuring
In addition to symbols, binding forms can include data structures such as vectors and maps. The result of the value expression will be destructured to match the binding form. For example:
(def nums (list 2 3 5 8 13 21)) (let [[a b c & the-rest] nums] (println "a is" a) (println "b is" b) (println "c is" c) (println "the-rest is" the-rest)) ;; a is 2 ;; b is 3 ;; c is 5 ;; the-rest is (8 13 21)
In this example, the binding form is the vector [a b c &
the-rest]
. It destructures the list nums
and assigns
a
to 2
, b
to 3
, and
so on. The special symbol &
collects all the remaining
elements into a list and binds it to the following symbol,
the-rest
.
The full syntax of destructuring is a rich and powerful mini-language of its own; refer to the Clojure language documentation for more details and examples.
Closures
Like JavaScript, ClojureScript supports lexical
closures. A function can refer to symbols defined in the
lexical scope in which it was created. Function
arguments and the let
form create lexical scopes. For
example:
(defn make-adder [n] (fn [x] (+ x n))) (def add4 (make-adder 4)) (def add7 (make-adder 7)) (add4 10) ;;=> 14 (add7 10) ;;=> 17
In this example, the make-adder
function returns
another function which “closes over” the value of n
in its
scope. We can use make-adder
to define new functions
add4
and add7
, which “remember” the binding of
n
that was in effect when they were created.
Flow Control
As we said at the start of this chapter, everything is an expression
in ClojureScript. That includes the control-flow expressions. For our
purposes, a flow-control expression is one that controls how its
components are evaluated. For example, an if
expression is
only going to evaluate one branch. This is what makes if
different from a function call, which always evaluates all of its
arguments.
This section introduces some of the most common flow-control expressions in ClojureScript. Some of them are special forms defined in the compiler, and some are macros defined in the core library, but the difference doesn’t matter at this point.
Conditional Branching
In ClojureScript, the basic conditional branch is represented by
an if
expression:
(if test-expr
then-expr
else-expr
)
The if
expression takes three subexpressions. First,
it will evaluate the
.
If the result of the test-expr
is logical true (see the sidebar on “Truthiness”),
then it will evaluate the test-expr
then-expr
,
otherwise it will evaluate the else-expr
. For
example:
(if (even? 42) (println "42 is even") (println "42 is odd"))
cond
It is possible to create multiple branches with nested
if
expressions, but it is more concise to use the
cond
macro instead:
(condtest-expr-1 body-expr-1 test-expr-2 body-expr-2 ...
:elseelse-expr
)
The cond
macro contains matched pairs of
test
and body
expressions. It
evaluates each test
expression in order. If one of
the test
expressions returns logical true, then
cond
evaluates the matching body
expression and returns. If none of the test
expressions returns logical true, then cond
returns
nil
.
It is possible to add a “default” case to a cond
expression by using the keyword :else
as a
test
expression. Since :else
is a
keyword, it evaluates to itself, and because it is neither
nil
nor false
, it is always logical true. In
fact, any logical true value would work, but it is conventional to use
the keyword :else
.
As an example, here is a conditional written first with nested
if
expressions and then with cond
:
(if (<= x 10) "x is a small number" (if (<= 11 x 100) "x is a medium-sized number" (if (<= 101 x 1000) "x is a big number" "x is a REALLY big number"))) (cond (<= x 10) "x is a small number" (<= 11 x 100) "x is a medium-sized number" (<= 101 x 1000) "x is a big number" :else "x is a REALLY big number")
The <
and >=
functions are the
numeric less-than and greater-than-or-equal-to comparisons. Like all
other functions in ClojureScript, they must be in function position,
so the expression (< x 10)
can be read “is
x
less than 10?” The expression (< 100 x
10000)
can be read “is 100 less than x
and
x
less than 10,000?”
Remember that whitespace is never significant in ClojureScript.
The test and body expressions in the cond
macro can be on
the same line or on different lines. All that matters is the order in
which they appear.
do
Each body inside an if
or cond
expression is limited to a single expression. Most of the time, when
writing “pure” functions without side effects, this is sufficient. But
sometimes side effects are necessary. ClojureScript’s do
expression allows multiple expressions to be used in the place of
one:
(do
... expressions ...
)
The do
form contains any number of expressions.
When evaluated, it evaluates each expression in order. It is similar
to JavaScript’s curly braces {}
, except that it also
returns a value. The return value of the last expression inside the
do
block is the return value of the entire
do
expression.
You can use a do
expression to write multiple
expressions in a place that normally only takes one expression. This
is commonly used when you need to write expressions that have side
effects:
(cond (< x 10) (do (println "x is a small number") :small) (< 100 x 1000) (do (println "x is a big number") :big) (>= x 10000) (do (println "x is a REALLY big number") :huge) :else (do (println "x is just a medium-sized number") :medium))
If the value of x
is less than 10, this expression
will print “x is a small number” and then return
the keyword :small
; if x
is between 100 and
10,000, it will print “x is a big number” and then
return the keyword :big
; and so on.
when
ClojureScript provides several built-in macros that combine
conditional expressions with an implicit
do
form. This includes the defn
macro for
defining functions. As another example, the when
macro
combines the if
and do
expressions:
(whencondition ... expressions ...
) ;; is the same as (ifcondition
(do... expressions ...
))
JavaScript Interop
ClojureScript, like Clojure, is designed to stay as close as
possible to the semantics of its host platform, only adding to them where
necessary. So ClojureScript strings are JavaScript String
objects, ClojureScript numbers are JavaScript Number
objects,
and ClojureScript functions are JavaScript Function
objects.
You can call JavaScript functions, methods, and constructors just like
calling any other function in ClojureScript.
The js Namespace
JavaScript, regrettably, has no concept of namespaces. Every function or variable defined in a JavaScript program lives in the same global scope. When two libraries want to use the same name, they often clash. Workarounds exist, such as using JavaScript objects as “modules” and defining things within the scope of anonymous functions, but they are just workarounds.
ClojureScript has built-in support for namespaces at the language
level: this is one of the places where it extends the semantics of the
host platform to provide a better developer experience. We will cover
namespaces more completely in Chapter 7 but one namespace
deserves special attention: the js
namespace. ClojureScript
uses the js
namespace to refer to the global scope of a
JavaScript program. Core JavaScript constructors such as
String
and Date
are accessed through this
namespace, as are browser-defined objects such as window
.
The following sections show examples of these.
Methods and Fields
ClojureScript can access methods and fields of JavaScript objects
directly. A JavaScript method invocation is written in ClojureScript as
a list beginning with the method name, prefixed with a .
(period). A field access is written similarly but prefixed with a
.-
(period and hyphen):
// JavaScript var message = "Hello, World!" var msg_length = message.length; var insult = message.replace(/World/, "idiots");
;; ClojureScript (def message "Hello, World!") (def msg-length (.-length message)) (def insult (.replace message #"World" "idiots"))
Note
In Clojure on the JVM, the same (.name object)
syntax was used for both method calls and field accesses. Since the
Java language does not allow a method and a field in the same class to
have the same name, there was never any ambiguity as to which was
intended. But in JavaScript, methods are also fields with functions as
values. To prevent ambiguity when calling methods from ClojureScript,
the (.-field object)
syntax was added for fields. This
syntax was later backported to Clojure on the JVM, first appearing in
release 1.4.0. Clojure on the JVM still accepts the (.name
object)
syntax for both fields and methods, but ClojureScript
always treats (.name object)
as a method call and
(.-name object)
as a field access.
Notice that the syntaxes for field access and method calls are
unified with ClojureScript’s syntax for function calls. The “target
object” on which a method or field is called no longer has a special
position before the method or field name; it becomes just another
argument in the function call. Method and field names are not given a
namespace qualifier like js/
because they are already
scoped within an object.
Constructor Functions
JavaScript constructor functions are also written as lists, but
with a .
(period or full stop)
appended to the name of the function:
// JavaScript var today = new Date(2012, 6, 16);
;; ClojureScript (def today (js/Date. 2012 6 16))
Notice that the Date
constructor is accessed through
the js
namespace, written js/Date
. The period
in js/Date.
(with no space in between the period and the
function name) tells the ClojureScript compiler that this expression
should compile to JavaScript’s new
operator.
Some built-in JavaScript functions, such as Number
and Date
, can be called as either a constructor function or
an ordinary function. Without the trailing period, the same function can
be invoked as an ordinary function, without the new
operator:
// JavaScript var today = Date();
;; ClojureScript (def today (js/Date))
Scope of this
Because of JavaScript’s lack of namespaces, it is common practice
to attach global functions to “module objects.” These functions can be
invoked using either the namespace-qualified syntax or method call
syntax. For example, if you were using the RaphaelJS library, [2] you could call its color
function like
this:
// JavaScript var green = Raphael.color("#00ff00");
;; ClojureScript (def green (Raphael/color "#00ff00"))
You could also invoke the color
function as a method
on the Raphael
object in the global JavaScript scope, like
this:
(defn green (.color js/Raphael "#00ff00"))
The difference comes in the handling of JavaScript’s
this
. The namespace-style syntax
(Raphael/color
) will compile to code that calls the
color
function with this
bound to
null
. The method-style syntax (.color
) will
invoke the color
function with this
bound to
the Raphael
object. The former is more natural in
ClojureScript code, but some JavaScript libraries depend on methods
being invoked with this
bound to the “module”
object.
Functions defined in JavaScript’s global scope, such as web
browsers’ built-in alert()
function, are accessed through
the js
namespace, as shown below. (Note that this example
may not work in Microsoft Internet Explorer, because IE’s JavaScript
implementation defines alert
as a special syntactic form,
not a normal JavaScript function.)
// JavaScript alert("Hello, World!");
;; ClojureScript (js/alert "Hello, World!")
Exceptions
ClojureScript has
try
/catch
/finally
and throw
forms that behave similarly to their JavaScript
equivalents. The
try
/catch
/finally
form looks like this:
(try;; ... body expressions ...
(catchErrClass err
;; ... handle an exception of type ErrClass ... ;; ... the exception object is bound to err ...
) (catch js/Errorerr
;; ... handle an exception of type Error ...
) (finally;; ... always execute this ...
))
Both the catch
and finally
blocks are
optional. Note that catch
in ClojureScript takes a “class”
(constructor function) and only handles exceptions of that class. You
can have multiple catch
blocks to handle different types of
exceptions. This mimics the exception-handling behavior of Clojure on
the JVM.
The throw
form takes an exception object and throws
it:
(throw (js/Error. "Houston, we have a problem."))
Although JavaScript and ClojureScript both permit you to throw
primitives such as strings, this is not recommended. When using
JavaScript exception types such as Error
, you will need to qualify them in both
throw
and catch
expressions as
js/Error
.
Summary
This chapter covered the essential syntax of the ClojureScript language. Most of this material is identical to Clojure on the JVM. We have not covered every kind of expression possible in ClojureScript, but any documentation written for Clojure on the JVM should apply equally well to ClojureScript.
JavaScript is already a dynamically-typed language with first-class functions, so some of these features may not be as unfamiliar to JavaScript programmers as they are for programmers accustomed to statically-typed languages such as Java.
There are many more features of the ClojureScript language that we did not have time to cover in this book. For example, multimethods, protocols, and records provide powerful mechanisms for polymorphism. These features are the same in both ClojureScript and Clojure, and there are many resources for them both online and in print.
Get ClojureScript: Up and Running now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.