Introduction to Functional Programming with Clojure
By: Max Vigdorchik
Tags: Functional Programming, Computer Science, Clojure, JVM, Lisp

Most student programmers do not get introduced to functional programming beyond some extremely academic course using Standard ML that does not really showcase it as a practical programming style for real projects. It is rare that someone learns Haskell or OCaml or Lisp as their introduction to programming as opposed to something like C or Java, despite the fact that it is no more complicated and in my opinion a more intuitive way to think about computation in general. I hope to demonstrate that in reality, functional programming can be a great practical choice for almost any project and is absolutely worth the time it takes to learn and transition from more popular languages.

Why Clojure?

I am using Clojure to showcase these concepts because it was designed from the ground up to be practical above all else, and accomplishes that practicality by embracing functional programming. Furthermore, the language is very “opinionated,” in that all of its design decisions are core to the language, and writing in a non-idiomatic style is heavily discouraged throughout the design. You can look at Clojure’s official rationale for all of the details, but we will be exploring various aspects of it throughout this article.

Another benefit of Clojure its Lisp syntax. Although totally alien to some, it is absolutely worth the effort to learn and get used to. Lisp is a family of languages originating in the 1960s that all have similar prefix notation with heavy use of parenthesis, as you will see. The beauty of Lisp is that its code is just a data structure that the language can interpret natively, also known as being homoiconic. This means that the fundamental syntax in Lisp is extremely bare bones, and can be expanded by libraries and code. It’s a jarring transition, but with a good editor and some time, it becomes hard to go back. As usual, Xkcd puts it best:

Basic Syntax and Functions

The goal of this section isn’t to teach enough Clojure to actually use it, there are plenty of tutorials for that already, but to introduce enough to understand the core syntax and any examples that follow. This is by no means complete or absolutely thorough, but it is surprisingly close to being complete which is the beauty of Lisp. We will not touch at all on Java or the JVM though, which is a very useful aspect of Clojure but not relevant to this discussion. It may seem like a lot of information, but if you keep in mind a comparison to other more popular languages, you will see that the syntax is practically tiny, and the standard library is a fraction of the size while being extremely expressive, if a bit difficult to grasp at first.

The first thing to understand before going any further is data in Clojure, much like in many other functional languages, is immutable. This means that you cannot write something like a for loop which must inherently mutate an index variable. Immutability means you no longer think of code as a series of instructions that change various values, and instead, think of code as a series of functions applied on functions, i.e. a series of transformations on data. These transformations can of course have side-effects, like printing to a terminal, because otherwise your program wouldn’t do anything, but functions with side-effects tend to be kept isolated where possible since it makes code much easier to understand and work with.

The fundamental action of any functional programming is, unsurprisingly, function application. In most programming languages, this is done with parenthesis following the function name, e.g. add(1,3) might return 4. Alternatively, it is common to use infix notation for binary functions, e.g. 1+3 will return 4 or 2 == 3 will return false. Infix usually has implicit operator precedence, so 1+2*3 is 7 whereas (1+2)*3 is 9. In Lisp, all of that is thrown away, and instead function application is done in the form of a list, also called an s-expression when used for function applications or nested data, where the first element is the function and the following elements are its arguments. So the previous examples are, in order: (+ 1 3) , (= 2 3) , (+ 1 (* 2 3)) , and (* (+ 1 2) 3) . In exchange for absolutely no ambiguity in function application (among other benefits we will see later), you get lots of parenthesis, with function definitions sometimes ending in 10 or more closing brackets, but that is easy enough to ignore.

Now that 70% of the syntax is out of the way, we can look at the rest. First of all comments use the semicolon symbol. The parenthesis structure represents a linked list. In order to prevent the list from being treated as function application, you can either add a quote, or use the list function: '(1 2 3) or (list 1 2 3) . Alternatively if a quote is too much syntax the function quote can be used instead: (quote (1 2 3)) . The difference between these two may not be clear at first, but the key is that the quote form prevents an s-expression from being treated as code, whereas list is just a function that takes values as an argument. These are generally not used unless trying to write code to modify other code, i.e. create your own syntax using macros. We will look at some examples of standard library macros shortly.

Instead of using lists to store sequential data, it is more common to use vectors (which are akin to ArrayLists in Java) as follows: [1 2 3] . Some examples of basic operations you would expect from a sequential data structure:

Note that in all cases commas to separate items are optional and normally excluded except in the case of long multi-line structures. The two other core data structures left in Clojure are the map (dictionary), and set. Maps are used to store key value pairs that can efficiently get a value given its key, and sets are a way to store a collection of values that are all unique. Both sets and maps can be stored in different ways internally, which can be controlled, but for our purposes we will not care about the difference between a hash set and sorted set (for example).

Clojure also includes all of the base data types you would expect, and a few more. There are strings, characters (denoted with a backslash [\a \b \c] is a vector of 3 characters), integers, floats, and booleans. Conversions between types happen implicitly as Clojure heavily embraces polymorphism, so using a function like first on a string returns a character. When converting to a boolean, every single value is considered truthy except for nil and false , including empty lists.

Clojure (and most Lisps) also support something called keywords, which are kind of like special strings used as keys in maps, denoted with a colon :mykeyword . The reason they are used as keys in maps is because they are implicitly converted to a function for accessing that element in a map. For example, (:a {:a "hi" :b "hello":c "hey"}) would return "hi" , but that would not work if the keys were something other than keywords.

The penultimate data type I am going to mention is one that has appeared a number of times already, and it is the symbol. Every function name we have seen so far like first is just a symbol referencing that function. These are effectively like pointers in C, or better yet they can be thought of as variable names. Like keywords, they are strings with special meaning attached to them, and can be converted to strings with the str function, and the other way around with the symbol function. By default, anytime you use a symbol, it will get replaced by the thing it references, which is why these functions actually do something. In order to prevent that and actually work with the symbols (which is something you rarely need to do), the quote macro is used just like with lists, e.g. 'first or (quote first) .

Finally and perhaps most importantly, there are functions. These are created using the fn macro, which takes a vector of symbols, which are the functions inputs, and then an s-expression that uses those symbols to do something. Note that fn itself is not a function per se, but a macro, meaning it takes all of its arguments as a data structure and operates on that code directly, which is why you can use variables inside the expression without them being directly evaluated. This happens at compile time, so all of these macros that are everywhere in Lisp will rearrange your code before the compiler sees it. This is a somewhat strange concept outside of Lisp, but it is how most more complex libraries are built up into programmer friendly interfaces, and how complex concepts can be expressed in such a bare bones syntax. It is like a much more powerful version of C preprocessor macros, made possible thanks to Lisp’s homoiconicity. Conceptually this may be a bit confusing, but in practice its extremely simple and looks the same as using any old function, so let’s see some examples.

Notice the consistency of the syntax here: it does not matter whether a function is named or not, putting it as the first element of an s-expression applies it regardless. That is what makes Lisp so great, it is fundamentally so simple (to paraphrase Clojure’s creator Rich Hickey, “simple but not necessarily easy”) that everything works exactly as you expect.

The last remaining thing to point out is that you can globally bind data (which includes functions) to a symbol using def . There are also ways to locally bind symbols (namely let binding) but we won’t concern ourselves with that for now. The defn macro combines def and fn into one form since defining functions is done so often.

Functional Programming

The key difference in all of this syntax compared to more common languages is that there is no idiomatic way to do one action followed by another, since data is immutable (there are constructs in the language to do that, specifically for handling functions with side effects like println ). Instead, programming should be thought of as a series of transformations of data. Lets say you want to double every element in an array. In a language like C, you would write a for loop that goes through each element of the array and multiplies it by 2, and then modifying the array with the result. If you then later use that array in some function, you must keep in your head as the programmer that at some time during the programs execution, that array’s meaning and value changed. Furthermore, in the process, a new variable needed to be declared to keep track of the current iteration in the loop. Sure, it has no performance impact and can be easily optimized away, but it’s extra code that does not have anything to do with the programmers actual goal.

Clojure (and basically every functional language) has a function called map that takes as input a function that takes 1 parameter, and a list. It then returns a list with that function applied to every element of the input list. Let’s see the Clojure version of the above code. Note that it is assumed that all of this code is put into the Clojure REPL (Read-Eval-Print Loop, much like the one python has, as well as terminals like Bash), since normally Clojure code is not just run sequentially like an imperative language would be.

map is a much weaker operation than the for loop, which is exactly why its so great. It inherently encodes the idea of taking an operation and using it identically on every element of a sequence, which is one of the most common uses of a loop anyway.

If you look back, you may notice that the operation of multiplying every element of an array by 2 could be made parallel easily, since no part of the computation depends on another. In fact, the map function in general satisfies that same property. Therefore, Clojure has a function called pmap , which is exactly like map but runs in parallel (this includes some overhead for collating all of the results together, so it runs slower on small lists). The above code can be parallelized with exactly one character due to the nature of the transformation we wanted to perform! For loops on the other hand could potentially depend on former results because they are stateful, so any attempt at parallelization will likely be somewhat more complex.

Constraining the amount you can do with a single function or construct is core to what makes functional programming practical. Complicated algorithms get reduced down to something where each step is mostly self explanatory. It’s all about how the data gets manipulated, rather than manipulating the data. Of course every once in a while state may actually be the easiest way to solve a problem, and Clojure provides ways to handle that (e.g. atoms, which can be very helpful for complex concurrent programs) if you need it, it’s just that you usually don’t need it. In fact, just to keep you honest, every single standard library function that involves state in any way ends with a ‘!’ to help avoid bugs that so often come with using stateful functions.