Jeff introduces Clojure fundamentals and uses them to show why you might want to explore this language further.
I mainly use Java at work in an enterprise setting, but I’ve been using Clojure at work for small tasks like extracting data from log files or generating or transforming Java code. What I do could be done with more traditional tools like Perl, but I like the readability of Clojure combined with its Java interoperability. I particularly like the different ways functions can be used in Clojure to manipulate data.
I will only be skimming the surface of Clojure in this short article and so will present a simplified view of the concepts. My goal is for the reader to get to know enough about Clojure to decide if it is worth pursuing further using longer and more complete introduction material already available.
I will start with a mini introduction to Clojure, followed by an overview of sequences and functions combination, and finish off with a real-world example.
Ultra Crash Course
Clojure, being a Lisp dialect, has program units inside lists. A function call will be the first element of a list, optionally followed by parameters.
For setup instructions, look here. Clojure programs can be run as a script from the command line, as a file from your IDE, or precompiled and packaged to be run as a normal Java jar. They can also be simply loaded or typed in the REPL, the interactive development shell. The REPL might be invoked from your IDE or simply called from the command line, provided you have java 1.5 or higher installed:
I invite you to follow along with a REPL on a first or second read and try the examples and variations. You can display the documentation of a function with the doc function.
Entering the following at the REPL:
will echo the documentation. For the article, I precede REPL output with the > symbol.
For the curious, you can also display the source of a function with source.
First, let’s start with the mandatory addition example.
Values can be associated to a symbol with def.
The REPL will write the symbol name preceded by the namespace, #'user/a, in this case.
Typing “a” will return back its value.
The symbol is bound to the result of the expression after its name.
The str function will concatenate the string representation of its arguments.
We can also string together characters. You’ll notice that character literals in Clojure are preceded by a backslash.
It is common to manipulate data as collections, be it lists, vectors, or whatever. The apply function will call the given function with the given collection unpacked.
(I will omit the echoing of the symbol name for the remainder of the article.)
Vectors are accessed like a function by passing the zero-based index as an argument.
Clojure has many core functions operating on sequences. A sequence allows uniform operations across different kinds of collections, be it a list, a vector, a string, etc. In our examples, we will be using mostly vectors, an array-like data structure with constant-time access.
For example, the take function will return the n first elements.
If you were expecting to get back the string “abc”, you might be disappointed by the result, as I was the first time I tried. What happened here? Operations producing sequences, like take, do not return elements in the original collection data type, but return a sequence of elements. That is why calling take on a string returns a sequence of characters. This means that take on the vector did not return a vector, but a sequence.
Let’s define a test vector to explore more sequence manipulations.
Oops! I forgot to capitalize the days. Let’s use map, which applies a function to each element of a collection and returns a sequence of the results. For example, the following returns a sequence of our numbers incremented by one.
First let’s develop a function to capitalize a word. Note that there already exists a capitalize function in the clojure.string namespace, but we’ll roll our own to demonstrate a few points. We’ll develop our function incrementally using the REPL.
We’ll start by getting the first letter of a word. The function first will create a sequence over the given collection and return the first element.
Let’s use a bit of Java interop and call the static function toUpperCase from the Java Character class.
So far so good. Now let’s get the rest of our word.
What happens if we want to string our capitalized word together?
We get back the string representation of the first argument, the letter W, concatenated with the string representation of the sequence of the rest of the word.
We need to use a variant of the function apply, which takes an optional number of arguments before a sequence of further arguments.
Now let’s make a function from our trials and tribulations.
The first line defined the function named capitalize taking one parameter named word. The second line is simply our original expression using the parameter.
Let’s try it out.
Good. We’re ready to capitalize each day of the week now.
Map is an example of a high-order function, which has one or more functions in its parameter list. It’s a convenient way of customizing a function’s behavior via another function instead of using flags or more involved methods like passing a class containing the desired behavior inside a method.
Notice that the original collection is left untouched.
Clojure collections are persistent, meaning they are immutable and that they share structure. Let’s add a day to have a longer weekend.
Adding Jupiday has not modified the original collection capitalized-days, which is guaranteed not to ever change, even by another thread. The longer week was not produced by copying the 7 standard days, but by keeping a reference to the 7 days and another to the extra day. Various collection "modifications", which really return a new data structure, are guaranteed to be as or almost as performant as the mutable version would be.
Filtering operations can be done with the filter high-order function, which return a sequence of elements satisfying the passed-in function.
When a function passed to an higher function is simple and only used once, there is no need to give it a name. We can define the function in-place. We just use fn instead of defn and forego specifying a name.
For example, here is another way of capitalizing our week days using an anonymous function.
Another handy sequence operation is reduce. It applies a function between the first two elements of a vector and then applies the function with the result and the 3rd element and so on.
Another form of reduce takes a parameter as the first value to combine with the first element.
Let’s sum the number of characters for each day.
We can redefine the previous anonymous function using syntactic sugar.
Note that we can omit the number 1 from the usage of the first argument.
Here is an example to extract the word three in three languages from a vector of vectors.
Composition of Functions
Let’s explore function assembly with a wild example: capitalize and stretch.
Let’s define our additional function.
This would be a standard way of combining stretch and capitalize.
Clojure also provides the comp function, which produce a new function from the successive application of the functions given.
Had we wanted to keep a capitalize-n-stretch function, we could have associated the result of the composition to a symbol.
We can compose more than one function together and we can even throw in anonymous functions into the mix.
We can produce a new function by partially giving arguments.
We can revisit our compose example differently.
A Real-World Example
Here is an example of a real function I wrote to collect all the referenced table names for a specific schema. The SQL statements are peppered in various Java files. I call the extract-table-names function for each file, and a corresponding .out file is produced with the referenced table names, uppercased, sorted, and without duplicates. After processing the file, the name of the file and the table count is returned to be displayed by the REPL. The goal is not for you to understand all the program, just to have a feel of it.
I’ve also used Clojure to extract running time statistics of our system and then generate distribution charts with Incanter, a wonderful interactive statistical platform.
This conclude my brief tour of data manipulation with Clojure. There is a lot more to sequences than what I’ve shown. For example, they are realized as needed, in what is referred to as lazy evaluation. There is an excellent summary of functions in the sequence section of the Clojure cheatsheet. Clojure functions can also be combined in other interesting ways like the thread-first or thread-last macros.
Jean-François “Jeff” Héon has been fascinated with programming ever since His parents got him a Commodore 64 in High School. He loves nagging his co-workers about new languages and frameworks. Jeff is most happy spending time with His wonderful wife and kid.