Explore Clojure maps as key-value pairs, including creation, access, and manipulation techniques for Java developers transitioning to Clojure.
Maps in Clojure are a fundamental data structure that represent collections of key-value pairs. They are similar to Java’s HashMap but come with the added benefits of immutability and functional programming paradigms. In this section, we will explore how to create, access, and manipulate maps in Clojure, drawing parallels to Java where applicable.
Maps in Clojure are immutable collections that store associations between keys and values. They are often used to represent structured data and are a core component of Clojure’s data manipulation capabilities. Unlike Java’s mutable HashMap, Clojure maps are immutable, meaning that any operation that modifies a map returns a new map with the modification applied, leaving the original map unchanged.
In Clojure, maps are created using curly braces {}. Keys and values are specified in pairs, separated by spaces. Here’s a simple example:
1(def person {:name "Alice" :age 30 :city "New York"})
In this example, :name, :age, and :city are keys, and "Alice", 30, and "New York" are their corresponding values. Notice the use of keywords (e.g., :name) as keys, which is a common practice in Clojure due to their efficiency and readability.
Java Equivalent:
In Java, you might use a HashMap to achieve similar functionality:
1import java.util.HashMap;
2import java.util.Map;
3
4Map<String, Object> person = new HashMap<>();
5person.put("name", "Alice");
6person.put("age", 30);
7person.put("city", "New York");
Key Differences:
HashMap is mutable.Accessing values in a Clojure map can be done using the get function or by invoking the map with the key directly. Here’s how you can access values:
1;; Using get
2(get person :name) ;=> "Alice"
3
4;; Direct key invocation
5(person :age) ;=> 30
Both methods are idiomatic in Clojure, but direct key invocation is often preferred for its brevity.
Java Equivalent:
In Java, accessing a value from a HashMap involves using the get method:
1String name = (String) person.get("name"); // "Alice"
2int age = (Integer) person.get("age"); // 30
Key Differences:
Clojure provides functions to add or remove entries from a map, returning a new map with the changes applied.
To add or update an entry in a map, use the assoc function:
1(def updated-person (assoc person :email "alice@example.com"))
This creates a new map updated-person with an additional :email entry.
Java Equivalent:
In Java, you would use the put method to add or update entries:
1person.put("email", "alice@example.com");
Key Differences:
assoc returns a new map, while put modifies the existing map.To remove an entry from a map, use the dissoc function:
1(def reduced-person (dissoc person :city))
This returns a new map without the :city entry.
Java Equivalent:
In Java, you would use the remove method:
1person.remove("city");
Key Differences:
dissoc returns a new map, while remove modifies the existing map.Clojure maps offer a variety of advanced operations that enhance their utility and flexibility.
You can merge multiple maps using the merge function:
1(def additional-info {:phone "123-456-7890" :city "Los Angeles"})
2(def merged-person (merge person additional-info))
This combines person and additional-info, with values from additional-info taking precedence in case of key conflicts.
Java Equivalent:
In Java, merging maps requires iterating over entries and adding them manually:
1Map<String, Object> additionalInfo = new HashMap<>();
2additionalInfo.put("phone", "123-456-7890");
3additionalInfo.put("city", "Los Angeles");
4
5person.putAll(additionalInfo);
Key Differences:
merge is more concise and expressive.merge returns a new map, while putAll modifies the existing map.Clojure allows you to filter maps using the filter function combined with a predicate:
1(def adults (filter (fn [[k v]] (and (= k :age) (>= v 18))) person))
This filters the map to include only entries where the age is 18 or older.
Java Equivalent:
In Java, filtering requires iterating over entries and conditionally adding them to a new map:
1Map<String, Object> adults = person.entrySet().stream()
2 .filter(entry -> entry.getKey().equals("age") && (Integer) entry.getValue() >= 18)
3 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
Key Differences:
To better understand map operations, let’s visualize the process of associating and dissociating entries in a map:
graph TD;
A[Original Map] -->|assoc :email "alice@example.com"| B[Updated Map];
B -->|dissoc :city| C[Reduced Map];
Diagram Explanation:
assoc.dissoc.To solidify your understanding, try modifying the following Clojure code:
:title, :author, and :year.:genre using assoc.:year key using dissoc.:make, :model, and :year. Add a new key :color and remove the :year key.By understanding and utilizing Clojure maps, you can effectively manage key-value data in a functional programming paradigm. Now that we’ve explored maps in Clojure, let’s apply these concepts to build more complex data structures and applications.