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:
(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:
import java.util.HashMap;
import java.util.Map;
Map<String, Object> person = new HashMap<>();
person.put("name", "Alice");
person.put("age", 30);
person.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:
;; Using get
(get person :name) ;=> "Alice"
;; Direct key invocation
(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:
String name = (String) person.get("name"); // "Alice"
int 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:
(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:
person.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:
(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:
person.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:
(def additional-info {:phone "123-456-7890" :city "Los Angeles"})
(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:
Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("phone", "123-456-7890");
additionalInfo.put("city", "Los Angeles");
person.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:
(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:
Map<String, Object> adults = person.entrySet().stream()
.filter(entry -> entry.getKey().equals("age") && (Integer) entry.getValue() >= 18)
.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:
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.