Quick Clojure Review

March 1, 2020

Clojure is a functional, symbiotic, and homoiconic programming language. - Functional: where functions are first-class citizens and mutating state is frowned upon - Symbiotic: the language is intended to be run atop a host. environment - Homoiconic: “code is data” — this helps facilitate a macro system for rewriting the language.

Data Structures

Clojure provides a language API based upon a select set of data structures. - List: (1 2 3) - Vector: [1 2 3] - Map: {:foo “bar”} - Set: #{1 2 3}

List

A list uses parentheses as its surrounding delimiters, and so an empty list would look like (), whereas a list with three elements could look like (“a” “b” “c”).

Clojure will happily hide those details away from you and provide abstractions that make dealing with data structures in the most appropriate and performant manner very easy.

By using the cons function, which will insert your value at the beginning of the list.

Or, conj function instead, which will pick the correct method for inserting the new value at the start of the list.

But, if your data structure were a vector collection instead, then the conj function would know to insert the value at the end of the collection.

There are other abstraction functions:

Vector

Vectors allow you to have index access to any element within the data structure.

You can modify the vector by using the assoc function (which is an abbreviation of “associate”). The way it works is that you provide the index of the vector you want to modify and then provide the value.

but what if you want to remove a value? One way to do this would be to use the pop function, which returns a copy of the vector but with the last element removed.

Map

The map data structure goes by many different names—hash, hash map, dictionary—and what distinguishes it from other data structures is the underlying implementation, which is a key part of ensuring the algorithmic performance of this particular data structure.

Keywords

Some readers may be wondering what the colon prefixing the key is supposed to mean. The colon indicates that the key is actually a keyword.

Keys, Values, and Replacement

The replace function allows you to create a new vector consisting of values extracted from a map data structure.

Set

A set is a data structure made up of unique values. Much like Clojure’s map and vector data structures, it provides Clojure with a very lightweight data model.

Functional Programming

  • Immutability
  • Referential transparency
  • First-class functions
  • Partial application
  • Recursive iteration
  • Composability

Immutability

If you have state and it can change, then once your application becomes distributed and concurrent, you’ll end up in a world of hurt, as many different threads can start manipulating your data at non-deterministic times. This can cause your application to fail at any given moment and become very hard to debug and to reason about. By offering immutability, Clojure can help to side-step this problem. In Clojure, every time you manipulate a data structure you are returned not a mutated version of the original, but rather a whole new copy with your change(s) applied. ## Referential transparency Referential transparency is when an expression can be replaced by its value without changing the behavior of a program.

The function sum (shown in Listing 3-1) is referentially transparent. No matter what happens, if I provide the same set of arguments (in this case 1 and 1), I’ll always get back the same result. ## First-class Functions For a language to offer “first-class functions,” it needs to be able to both store functions and pass functions around as if they were values. We’ve already seen the former being achieved using variables, and the latter (passing functions around as values) is also possible within Clojure. - complement - apply - map - reduce - filter - comp

complement

apply

map

reduce

filter

comp

Partial application

Partial application helps to promote the creation of functions that can expand their use cases beyond their initial intent. The concept of partial application is regularly confused with another functional concept known as currying (which Clojure doesn’t support). When you “curry” a function, the function’s arguments are expanded internally into separate functions. A curried function won’t execute its body until all arguments have been provided (similar to partial application). So, again, if your function accepted three arguments you could effectively call your curried function in one of the following ways.

So, just to recap, the main differences between currying and partial application are as follows. 1. You only partially apply your values once. So, if your function takes three arguments and you partially apply two of them, then when your resulting function is called you only provide one argument. If you had instead partially applied only one argument, you would still only call the resulting function once (but this time you would have to provide the remaining two arguments). 2. If we consider the “API” scenario from earlier, you are providing the initial values for the partially applied function, whereas with a curried function it is the user who provides the arguments.

Recursive Iteration

The classic for loop you’re likely familiar with for (i = 0; i < 10; i++) {} by design allows mutating local variables to increment the loop. In Clojure, local variables are immutable, and so for us to loop we need to use recursive function calls instead. Instead of looping, you’ll typically need to use the loop/recur special form, although a lot of the time other iterator-style functions such as map, reduce, and filter will be better fitted to solving the problem at hand. The main benefit of the loop/recur special form is that it allows you to safely apply recursive function calls without exhausting your memory stack. For example, if you’ve ever written any JavaScript code in your life you’ll likely have hit a problem at least once where you’ve exhausted the stack and caused a “stack overflow” error.

Resolving the problem with the code will require a process that the else statement need to be modified so that instead of returning a function call to count-down you return a function.

Remember: #(…) is a shorthand syntax for an anonymous function. ## Composability The main reason this is such a key aspect of functional programming is that your units of functionality should be generic enough to be reused within many different contexts, rather than being overly specific to one environment and ultimately not being reusable.