Learn the heart of the Clojure language
Clojure is based on collections, but how are they used? What are some patterns for making the most of them? This course introduces you to the workhorses of the Clojure programming language, the immutable collections.
This course has four lessons now, focused on hash maps and vectors. I'll add to this over time.
Want this course?
Love it or leave it guarantee
If you don't learn as much from this course as you thought, just ask for a refund within 30 days and I'll give you your money back.
File downloads for this course are below. Jump down
Introduction to Clojure's Collections
We go over the main thought process you need to master Clojure's collection. We also review the structure of the course.
We go over the stuff that's common for all of the data structures. Namely, that they are immutable and persistent.
Vectors are a super common data structure in Clojure. They're super useful. They have a literal representation, give you random-access to all the elements inside, and they maintain order. We go over how to make vectors, how to use them, and some of their interesting properties.
HashMaps are the workhorse data structure of Clojure. They're used everywhere to add structure and meaning to data. We go over how to make them, how to use them, and some of the properties that make them so useful.
Sets are incredibly useful. They give you fast containment checks and they don't remember duplicates. They're very useful for adding idempotence since duplicates are ignored.
Lists are a persistent linked list implementation. They give you sequential access, but new items add to the beginning.
Queues are not used very often in Clojure. There is no literal syntax and there are more performant, mutable, thread-safe queues available when you need to communicate between threads. However, sometimes it's exactly what you need. Queues are ordered sequential collections that add to the end and remove from the beginning.
Sorted Map collection
Sorted Maps are just like HashMaps but they keep their keys sorted. You can pass in a comparator to the constructor to define a custom sort order.
Sorted Set collection
Sorted Sets are just like Sets except they keep their values sorted. You can pass in a custom comparator to define your own sort order.
What do I mean by access patterns? It's the underlying commonality between the collections. It answers the question: How will we access our information?
Sequential Access Pattern
A super common way to access items in Clojure collections is through the seq abstraction. The seq abstraction gives us the items in a collection one at a time. But what order do those come in? The order is determined by the collection itself. We look at the different options for order. which largely determine the choice of collection type we use.
Remembering Duplicates Access Pattern
Another common access pattern that largely determines the choice of collection is whether you want to remember duplicates. What happens when duplicates occur? Do you remember them? Do you replace the old one? Do you keep the old one?
Lookup by key Access Pattern
It is a very useful access pattern to look up a value given another key. We use HashMaps, Vectors, and Sets to implement this pattern. Each data type has its own semantics.
Associate key and value Access Pattern
We look at another common access pattern, which is to associate a key and a value into a collection. The two collections that this works with are HashMap and Vector. Combined with Lookup value by key, we can do a convenient update operation.
Dissociate a key and value Access Pattern
Sometimes we want to remove a key/value pair from a HashMap. We use the dissoc operator to do that.
Count the elements Access Pattern
A common access pattern that we often don't think about is to count the elements. We don't think about it because all of the collections implement it. But there is one big gotcha you should know about.
Equality Comparison Access Pattern
Clojure obeys the Java standard library's equality partitions. Namely, sequential, map, and set partitions for collections. These allow us to swap out different concrete implementations of our collections while still having equivalent logical collections.
Removing an item from a set Access Pattern
Clojure gives us an operation to remove an element from a set. We pass in the set and the element, and it's gone!
Splitting a sequence Access Pattern
Normally, splitting a sequence requires a take and a drop, which get slower as the number of elements gets bigger. However, Vectors provide a fast way to split off a sub vector.
Containment Check Access Pattern
We can quickly check if an item is contained in a set. We can also check quickly if a key is in a Map, or if an index will work for a Vector.
First-in, first-out Access Pattern
A useful access pattern is to add stuff on one and and take it off the other end. This gives you a way to take stuff off in the same order you put it in on, like a queue. Clojure provides a collection called Queue for just that purpose.
Last-in, First-out Access Pattern
If you want to access a collection like a stack, there are two options to choose from: Lists and Vectors. We look at how to use them.
There are many common usage patterns that Clojure programmers use to quickly decide what collection to use. Learning the usage patterns will help you read other people's code.
Entity Usage Pattern
In Clojure, we use the Entity pattern all the time. Usually, it's the first thing we notice when learning Clojure. The Entity pattern essentially means we use a HashMap to represent information about an entity. We look at how that works, and then we go over a variety of the Entity patterns called Variant Entity. We also look at a code smell and an anti-pattern.
Index Usage Pattern
We look at another usage pattern for HashMaps. This one is for looking things up based on a key. We look at three variations on it.
Tuple Usage Pattern
We look at another typical usage pattern. This one uses Vectors to hold bits of data together. It's a replacement for Entities (which use a HashMap), but it has a lot of limitations, which we go over. WE talk about the Variant Tuple variation.
Multi-comparison Usage Pattern
A very common pattern you'll see in Clojure code in the wild is using a Set to do multiple comparisons in constant time. You can exploit the fact that Sets can be called as functions to make a quick predicate.
When you need to modify an immutable value, you actually make a modified copy. If you make a lot of changes, you make a copy for each change. If you're just going to throw those copies away, that is a major waste. Transients let you create a local mutable copy of a collection, modify it, and then freeze it. When used with large-batch modifications, it can significantly improve performance.
Usage in an Atom
Most collection operations work really well in an Atom. But some of them don't because they need to be used in pairs. One such pair is peek/pop. We go over how to use them with an atom. It's anything but obvious.
Sometimes a single collection doesn't satisfy all of the access patterns you need. For example, what if you want a collection that remembers order and lets you set values for keys? You will need to construct it out of the existing one.
This is an advanced topic, but interesting nonetheless! Instead of implementing a hybrid using a HashMap, we build our own type. We explore the Clojure implementation of Vector and replicate the methods and interfaces it implements.
Vectors and Lists in syntax
Why is it that Clojure syntax has both parens and square brackets? What's the difference? For instance, a let form has parens around it but the bindings are in square brackets. The key is that the stuff in square brackets is configuring the let form. That's the difference.
Lazy sequences are a complex issue with a lot of gotchas. I'd like you to understand how they work and know that you could have them passed to your function. Can you handle them?
Collections vs sequences
When I first was learning Clojure, something was confusing: some operations, like map, took the collection as the last argument, and some, like conj, took the collection as the first argument. It was frustrating. But then I learned that they are in two different categories of functions. Learning that fact helped me be more productive in the language.