Explore Clojure's core data structures: vectors, lists, maps, and sets. Learn their usage, performance characteristics, and operations with detailed examples for Java developers.
In this section, we delve into the core data structures of Clojure: vectors, lists, maps, and sets. These structures are foundational to functional programming in Clojure and offer unique advantages over their Java counterparts. By understanding these data structures, you can leverage Clojure’s strengths to build scalable and efficient applications.
Vectors in Clojure are akin to Java’s ArrayList
, providing efficient indexed access and updates. They are immutable, meaning any modification results in a new vector, preserving the original.
;; Creating a vector
(def my-vector [1 2 3 4 5])
;; Accessing elements
(println (my-vector 2)) ; Output: 3
;; Adding an element
(def new-vector (conj my-vector 6))
(println new-vector) ; Output: [1 2 3 4 5 6]
;; Updating an element
(def updated-vector (assoc my-vector 1 10))
(println updated-vector) ; Output: [1 10 3 4 5]
In Java, an ArrayList
provides similar indexed access, but it is mutable. Clojure’s vectors offer immutability, which is crucial for functional programming paradigms.
// Java ArrayList example
List<Integer> arrayList = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
arrayList.set(1, 10); // Mutates the list
System.out.println(arrayList); // Output: [1, 10, 3, 4, 5]
Lists in Clojure are linked lists, optimized for sequential access and operations at the head. They are ideal for scenarios where you frequently add or remove elements from the front.
;; Creating a list
(def my-list '(1 2 3 4 5))
;; Accessing the first element
(println (first my-list)) ; Output: 1
;; Adding an element to the front
(def new-list (cons 0 my-list))
(println new-list) ; Output: (0 1 2 3 4 5)
;; Removing the first element
(def rest-list (rest my-list))
(println rest-list) ; Output: (2 3 4 5)
Java’s LinkedList
provides similar functionality but is mutable. Clojure’s lists maintain immutability, which is beneficial for functional programming.
// Java LinkedList example
LinkedList<Integer> linkedList = new LinkedList<>(Arrays.asList(1, 2, 3, 4, 5));
linkedList.addFirst(0); // Mutates the list
System.out.println(linkedList); // Output: [0, 1, 2, 3, 4, 5]
Maps in Clojure are key-value pairs, similar to Java’s HashMap
. They provide efficient lookups and updates, with immutability ensuring thread safety.
;; Creating a map
(def my-map {:a 1 :b 2 :c 3})
;; Accessing a value
(println (:b my-map)) ; Output: 2
;; Adding a key-value pair
(def new-map (assoc my-map :d 4))
(println new-map) ; Output: {:a 1, :b 2, :c 3, :d 4}
;; Removing a key-value pair
(def updated-map (dissoc my-map :a))
(println updated-map) ; Output: {:b 2, :c 3}
Java’s HashMap
allows similar operations but is mutable. Clojure’s maps provide immutability, enhancing safety in concurrent environments.
// Java HashMap example
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put("a", 1);
hashMap.put("b", 2);
System.out.println(hashMap.get("b")); // Output: 2
Sets in Clojure are collections of unique elements, akin to Java’s HashSet
. They are useful for ensuring uniqueness and performing set operations.
;; Creating a set
(def my-set #{1 2 3 4 5})
;; Checking membership
(println (contains? my-set 3)) ; Output: true
;; Adding an element
(def new-set (conj my-set 6))
(println new-set) ; Output: #{1 2 3 4 5 6}
;; Removing an element
(def updated-set (disj my-set 1))
(println updated-set) ; Output: #{2 3 4 5}
Java’s HashSet
provides similar functionality but is mutable. Clojure’s sets maintain immutability, which is advantageous for functional programming.
// Java HashSet example
Set<Integer> hashSet = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5));
hashSet.add(6); // Mutates the set
System.out.println(hashSet); // Output: [1, 2, 3, 4, 5, 6]
Clojure provides a rich set of operations for manipulating these data structures. Let’s explore some common functions:
conj
: Adds an element to the end.assoc
: Updates an element at a specific index.subvec
: Creates a subvector from a range.cons
: Adds an element to the front.first
: Retrieves the first element.rest
: Returns the list without the first element.assoc
: Adds or updates a key-value pair.dissoc
: Removes a key-value pair.get
: Retrieves a value by key.conj
: Adds an element.disj
: Removes an element.union
: Combines two sets.To better understand these data structures, let’s visualize them using diagrams.
graph TD; A[Vector] -->|Indexed Access| B[Element] C[List] -->|Sequential Access| D[Element]
Diagram 1: Vectors provide indexed access, while lists offer sequential access.
graph TD; E[Map] -->|Key-Value| F[Pair] G[Set] -->|Unique Elements| H[Element]
Diagram 2: Maps store key-value pairs, while sets ensure unique elements.
Experiment with the provided code examples by modifying them. For instance, try adding elements to a list and observe the changes. Explore how immutability affects the behavior of these data structures.
In this section, we’ve explored Clojure’s core data structures: vectors, lists, maps, and sets. These structures offer immutability, efficiency, and safety in concurrent environments, making them ideal for functional programming. By understanding their characteristics and operations, you can leverage Clojure’s strengths to build scalable applications.