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 test-expr. If the result of the test-expr is logical true (see the sidebar on “Truthiness”), then it will evaluate the 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:

(cond test-expr-1 body-expr-1
      test-expr-2 body-expr-2
      ...
      :else else-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:

(when condition
  ... expressions ...)

;; is the same as
(if condition
  (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 ...
  (catch ErrClass err
    ;; ... handle an exception of type ErrClass ...
    ;; ... the exception object is bound to err ...
    )
  (catch js/Error err
    ;; ... 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.