Clojure Coding Quick Start

Clojure is built and distributed in a Java archive (a jar file) that can be run on the Java Virtual Machine (JVM). Running programs on the Java Virtual Machine requires assembling a classpath that contains all of the code required to run your program, which includes Clojure itself, your code, and any Java or Clojure dependencies needed by your code.

To run Clojure and the code in this book, you need two things:

  • A Java runtime. Download[8] and install Java version 6 or higher (however, using at least version 8 is recommended for improved performance).

  • Clojure command line tools. The Clojure “Getting Started” page[9] contains the latest instructions on installing the Clojure command line tools. These tools have support for managing library dependencies and running programs with the appropriate classpath.

You will use the command line tools (specifically clj) to install Clojure and all of the dependencies for the sample code in this book. For more details on basic usage of clj, see the guide[10] and reference[11] pages. Don’t worry about learning everything now, though, because this book will guide you through the commands necessary to follow along.

While you’re working through the book, use the version of Clojure tied to the book’s sample code. See Downloading Sample Code for instructions on downloading the sample code.

You can test your install by navigating to the directory where you placed the sample code and running a Clojure read-eval-print loop (REPL) using the clj tool:

 clj

The clj tool uses an optional configuration file, deps.edn, to declare dependencies. When clj starts, it will download any necessary dependencies (and their transitive dependencies). If you have just installed the clj tool, you may see statements about Clojure and other dependencies being downloaded. Once the REPL starts, it prints some helpful version information, then prompts you with:

 user=>

Now you are ready for “Hello World.”

Using the REPL

To see how to use the REPL, let’s create a few variants of “Hello World.” First, type (println "hello world") at the REPL prompt:

 user=> (println "hello world")
 ->​​ ​​hello​​ ​​world
 ->​​ ​​nil

The second line, hello world, is the console output you requested. The third line is the return value from the println expression.

Next, encapsulate your “Hello World” into a function that can address a person by name:

 (​defn​ hello [name] (str ​"Hello, "​ name))
 -> #​'user/hello

Let’s break this down:

  • defn defines a function.

  • hello is the function name.

  • hello takes one argument, name.

  • str is a function call that concatenates an arbitrary list of arguments into a string.

  • defn, hello, name, and str are all symbols, which are names that refer to things. Legal symbols are defined in Symbols.

Look at the return value, #’user/hello. The prefix #’ indicates that the function was stored in a Clojure var, and user is the namespace of the function. (The user namespace is the REPL default, like the default package in Java.) You don’t need to worry about vars and namespaces yet; they’re covered in Vars, Bindings, and Namespaces.

Now you can call hello, passing in your name:

 user=>​ (hello ​"Stu"​)
 -> ​"Hello, Stu"

If you get your REPL into a state that confuses you, the simplest fix is to kill the REPL with Ctrl+C on Windows or Ctrl+D on *nix and then start another one.

Special Variables

The REPL includes several useful, special variables. When you’re working in the REPL, the results of evaluating the three most recent expressions are stored in the special variables *1, *2, and *3, respectively. This makes it easy to work iteratively. Say hello to a few different names:

 user=>​ (hello ​"Stu"​)
 -> ​"Hello, Stu"
 
 user=>​ (hello ​"Clojure"​)
 -> ​"Hello, Clojure"

You can use the special variables to combine the results of your recent work:

 (str *1 ​" and "​ *2)
 -> ​"Hello, Clojure and Hello, Stu"

If you make a mistake in the REPL, you’ll see a Java exception. The details are often omitted for brevity. For example, dividing by zero is a no-no:

 user=>​ (/ 1 0)
 -> ArithmeticException Divide by zero clojure.lang.Numbers.divide (Numbers.java:158)

Here the problem is obvious, but sometimes the problem is more subtle and you want the detailed stack trace. The *e special variable holds the last exception. Because Clojure exceptions are Java exceptions, you can ask for the stack trace by calling pst (print stack trace).

 user=>​ (pst)
 -> ArithmeticException Divide by zero
 | clojure.lang.Numbers.divide (Numbers.java:158)
 | clojure.lang.Numbers.divide (Numbers.java:3808)
 | user/eval1247 (form-init5722005119880062985.clj:1)
 | user/eval1247 (form-init5722005119880062985.clj:1)
 | clojure.lang.Compiler.eval (Compiler.java:6927)
 | clojure.lang.Compiler.eval (Compiler.java:6890)
 | clojure.core/eval (core.clj:3105)
 | clojure.core/eval (core.clj:3101)
 | clojure.main/repl/read-eval-print--7408/fn--7411 (main.clj:240)
 | clojure.main/repl/read-eval-print--7408 (main.clj:240)
 | clojure.main/repl/fn--7417 (main.clj:258)
 | clojure.main/repl (main.clj:258)

Java interop is covered in Chapter 10, Java Interop.

If you have a block of code that’s too large to conveniently type at the REPL, save the code into a file, and then load that file from the REPL. You can use an absolute path or a path relative to where you launched the REPL:

 ; save some work in temp.clj, and then ...
 user=>​ (load-file ​"temp.clj"​)

The REPL is a terrific environment for trying ideas and getting immediate feedback. Keep a REPL open at all times while you read this book.

Adding Shared State

The hello function of the previous section is pure; that is, it has no side effects. Pure functions are easy to develop, test, and understand, and you should prefer them for many tasks.

That said, most programs have some shared state and will use impure functions to manage that shared state. Let’s extend hello to keep track of past visitors. First, you need a data structure to track the visitors. A set will do the trick:

 #{}
 -> #{}

The #{} is a literal for an empty set. Next, you need conj:

 (conj coll item)

conj is short for conjoin, and it builds a new collection with an item added. conj an element onto a set to see that a new set is created:

 (conj #{} ​"Stu"​)
 -> #{​"Stu"​}

Now that you can build new sets, you need some way to keep track of the current set of visitors. Clojure provides several reference types (refs) for this purpose. The most basic reference type is the atom:

 (atom initial-state)

To name your atom, you can use def:

 (​def​ symbol initial-value?)

def is like defn but more general. A def can define functions or data. Use atom to create an atom, and use def to bind the atom to the name visitors:

 (​def​ visitors (atom #{}))
 -> #​'user/visitors

To update a reference, you must use a function, such as swap!:

 (swap! r update-fn & args)

swap! applies an update-fn to reference r, with optional args if necessary. Try to swap! a visitor into visitors, using conj as the update function:

 (swap! visitors conj ​"Stu"​)
 -> #{​"Stu"​}

atom is one of several reference types in Clojure. Choosing the appropriate reference type requires care (discussed in Chapter 6, State and Concurrency).

At any time, you can peek inside the ref with deref or with the shorter @:

 (deref visitors)
 -> #{​"Stu"​}
 
 @visitors
 -> #{​"Stu"​}

Now you’re ready to build the new, more elaborate version of hello:

 (​defn​ hello
 "Writes hello message to *out*. Calls you by username.
  Knows if you have been here before."
  [username]
  (swap! visitors conj username)
  (str ​"Hello, "​ username))

Next, check that visitors are correctly tracked in memory:

 (hello ​"Rich"​)
 -> ​"Hello, Rich"
 
 @visitors
 -> #{​"Aaron"​ ​"Stu"​ ​"Rich"​}

In all probability, your visitors list is different from the one shown here. That’s the problem with state! Your results will vary, depending on when things happened. You can reason about a function with direct local knowledge. Reasoning about state requires a full understanding of history.

Avoid state where possible. But when you need it, make it sane and manageable by using refs such as atoms. Atoms (and all other Clojure reference types) are safe for multiple threads and processors. Better yet, this safety comes without any need for locks, which are notoriously tricky to use.

At this point, you should feel comfortable entering small bits of code at the REPL. Larger units of code aren’t that different; you can load and run Clojure libraries from the REPL as well. Let’s explore that next.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.202.27