Browse Clojure Foundations for Java Developers

Clojure Practical Examples and Exercises for Java Developers

Explore practical Clojure examples and exercises designed for Java developers transitioning to functional programming. Learn through hands-on coding and interactive REPL sessions.

3.9 Practical Examples and Exercises§

Welcome to the practical section of our Clojure guide, where we will solidify your understanding of Clojure’s fundamental syntax and concepts through hands-on examples and exercises. As experienced Java developers, you already have a strong foundation in programming. This section will leverage that knowledge to help you transition smoothly into Clojure’s functional programming paradigm.

3.9.1 Simple Functions and Data Manipulations§

Let’s start by exploring how to create simple functions and manipulate data in Clojure. We’ll compare these examples with Java to highlight the differences and similarities.

Defining Functions§

In Java, you define a method within a class. In Clojure, functions are first-class citizens and can be defined using the defn keyword.

Java Example:

public class Example {
    public static int add(int a, int b) {
        return a + b;
    }
}

Clojure Equivalent:

(defn add [a b]
  (+ a b))
  • Explanation: The defn keyword is used to define a function named add that takes two parameters, a and b, and returns their sum using the + operator.

Data Manipulation with Collections§

Clojure provides powerful data structures like lists, vectors, maps, and sets. Let’s see how we can manipulate these collections.

Java Example:

import java.util.Arrays;
import java.util.List;

public class Example {
    public static List<Integer> doubleValues(List<Integer> numbers) {
        return numbers.stream()
                      .map(n -> n * 2)
                      .collect(Collectors.toList());
    }
}

Clojure Equivalent:

(defn double-values [numbers]
  (map #(* 2 %) numbers))
  • Explanation: The map function applies a given function to each element of the collection. Here, #(* 2 %) is an anonymous function that doubles each number.

Try It Yourself: Modify the double-values function to filter out odd numbers before doubling them.

3.9.2 Exercises: Creating and Using Different Data Types§

Now, let’s practice creating and using different data types in Clojure. We’ll explore lists, vectors, maps, and sets.

Lists and Vectors§

Lists and vectors are sequential collections in Clojure. Lists are linked lists, while vectors are indexed collections.

Exercise: Create a list of numbers and a vector of strings. Write a function to concatenate the vector elements into a single string.

Solution:

(def numbers '(1 2 3 4 5))
(def words ["Hello" "world" "from" "Clojure"])

(defn concatenate-words [words]
  (clojure.string/join " " words))

(concatenate-words words) ; => "Hello world from Clojure"
  • Explanation: The clojure.string/join function concatenates the elements of the vector words into a single string with spaces.

Maps and Sets§

Maps are key-value pairs, and sets are collections of unique elements.

Exercise: Create a map of student names to their grades and a set of subjects. Write a function to add a new student to the map.

Solution:

(def students {"Alice" 90, "Bob" 85})
(def subjects #{"Math" "Science" "History"})

(defn add-student [students name grade]
  (assoc students name grade))

(add-student students "Charlie" 92) ; => {"Alice" 90, "Bob" 85, "Charlie" 92}
  • Explanation: The assoc function adds a new key-value pair to the map.

Try It Yourself: Modify the add-student function to update a student’s grade if they already exist in the map.

3.9.3 Experimenting in the REPL§

The REPL (Read-Eval-Print Loop) is a powerful tool for experimenting with Clojure code. Let’s use it to explore some interactive exercises.

REPL Basics§

Start the REPL and try evaluating simple expressions. For example:

(+ 1 2 3) ; => 6
  • Explanation: The REPL evaluates the expression and returns the result.

Defining Functions in the REPL§

Define a function in the REPL and test it with different inputs.

(defn greet [name]
  (str "Hello, " name "!"))

(greet "Alice") ; => "Hello, Alice!"
  • Explanation: The str function concatenates strings.

Try It Yourself: Define a function in the REPL that takes a list of names and returns a greeting for each name.

3.9.4 Comparing Clojure and Java§

Let’s compare some common tasks in Clojure and Java to understand how Clojure’s functional approach simplifies code.

Filtering Collections§

Java Example:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Example {
    public static List<Integer> filterEvenNumbers(List<Integer> numbers) {
        return numbers.stream()
                      .filter(n -> n % 2 == 0)
                      .collect(Collectors.toList());
    }
}

Clojure Equivalent:

(defn filter-even-numbers [numbers]
  (filter even? numbers))
  • Explanation: The filter function in Clojure takes a predicate function (even?) and a collection, returning a new collection of elements that satisfy the predicate.

Summing Numbers§

Java Example:

import java.util.Arrays;
import java.util.List;

public class Example {
    public static int sumNumbers(List<Integer> numbers) {
        return numbers.stream()
                      .mapToInt(Integer::intValue)
                      .sum();
    }
}

Clojure Equivalent:

(defn sum-numbers [numbers]
  (reduce + numbers))
  • Explanation: The reduce function applies a binary function (+) cumulatively to the elements of the collection, reducing it to a single value.

Try It Yourself: Modify the sum-numbers function to calculate the product of the numbers instead of the sum.

3.9.5 Visualizing Data Flow and Immutability§

To better understand Clojure’s data flow and immutability, let’s visualize these concepts using diagrams.

Data Flow Through Higher-Order Functions§

  • Caption: This diagram illustrates the flow of data through a series of higher-order functions: map, filter, and reduce.

Immutability and Persistent Data Structures§

    graph TD;
	    A[Original Collection] -->|assoc| B[New Collection];
	    A -->|dissoc| C[Another New Collection];
  • Caption: This diagram shows how Clojure’s persistent data structures allow for efficient creation of new collections without modifying the original.

3.9.6 Exercises and Practice Problems§

Let’s reinforce your learning with some exercises and practice problems.

Exercise 1: Transforming Data§

Write a function that takes a list of numbers and returns a list of their squares, excluding negative numbers.

Solution:

(defn square-non-negative [numbers]
  (->> numbers
       (filter (fn [n] (>= n 0)))
       (map (fn [n] (* n n)))))
  • Explanation: The ->> macro threads the collection through the functions filter and map.

Exercise 2: Managing State§

Create a function that simulates a simple bank account. It should take an initial balance and a list of transactions (positive for deposits, negative for withdrawals) and return the final balance.

Solution:

(defn calculate-balance [initial-balance transactions]
  (reduce + initial-balance transactions))

(calculate-balance 100 [20 -10 50 -30]) ; => 130
  • Explanation: The reduce function accumulates the transactions starting from the initial balance.

Try It Yourself: Modify the calculate-balance function to reject transactions that would result in a negative balance.

3.9.7 Summary and Key Takeaways§

In this section, we’ve explored practical examples and exercises to reinforce your understanding of Clojure’s syntax and concepts. We’ve compared Clojure with Java to highlight the benefits of functional programming, such as concise code and powerful data manipulation capabilities.

Key Takeaways:

  • Clojure’s functions are first-class citizens, allowing for concise and expressive code.
  • Immutability and persistent data structures enable safe and efficient data manipulation.
  • The REPL is a powerful tool for experimenting and testing code interactively.
  • Higher-order functions like map, filter, and reduce simplify data processing tasks.

Now that we’ve explored these practical examples, let’s continue to build on this foundation as we delve deeper into Clojure’s advanced features and capabilities.

Clojure Practical Examples Quiz§