Lesson 1
Currying
Welcome to Currying

Welcome to the unit on Currying in Java! As part of our Advanced Functional Programming Techniques course, this lesson will introduce you to the concept of currying and demonstrate its practical applications in Java. We'll explore how currying can be leveraged to create more modular, reusable, and maintainable code.

What You'll Learn

In this lesson, you'll discover how to:

  • Define and utilize curried functions in Java.
  • Understand the syntax and structure involved.
  • Apply currying to both numerical operations and object creation.
Currying Explained

Currying is a powerful technique that allows you to transform a function with multiple arguments into a series of functions that each take a single argument. This not only simplifies the function calls but also enables partial application, where you can preset some arguments and delay others for later. By breaking down functions in this way, currying helps create more modular and reusable code. Let’s explore this concept through two practical examples: one demonstrating numerical operations and another focusing on object creation.

Example 1: Currying for Addition

Let's start with a basic arithmetic example to illustrate how currying works. In this case, we'll be using currying to break down a simple addition operation:

Java
1Function<Integer, Function<Integer, Integer>> curriedAddition = a -> b -> a + b; 2 3Function<Integer, Integer> addTen = curriedAddition.apply(10); 4 5int sumResult = addTen.apply(5); 6 7System.out.println("Sum Result: " + sumResult); // Outputs: Sum Result: 15

In the code above, we first define a curried function curriedAddition. This function is structured to take two integers, but instead of taking both at once, it takes the first integer (a) and returns a new function that then takes the second integer (b). When b is provided, the two integers are added together.

Now, let’s break this down further:

  1. Creating the Curried Function:

    Java
    1Function<Integer, Function<Integer, Integer>> curriedAddition = a -> b -> a + b;

    This line defines the curried function. Here, a -> b -> a + b means that curriedAddition takes an integer a and returns a function that takes another integer b, which adds b to a.

  2. Partial Application:

    Java
    1Function<Integer, Integer> addTen = curriedAddition.apply(10);

    By applying the first argument (10), we create a new function called addTen. This new function is a partially applied version of the original curriedAddition function—it’s now a specialized function that adds 10 to any number it’s given.

  3. Final Application and Result:

    Java
    1int sumResult = addTen.apply(5);

    When we apply 5 to addTen, it completes the operation, adding 5 to 10 and resulting in 15. Finally, we print the result:

    Java
    1System.out.println("Sum Result: " + sumResult); // Outputs: Sum Result: 15
Example 2: Currying for Object Creation

Currying isn’t limited to simple arithmetic—it’s also a powerful tool for object creation.

First, let's define the Person class that we'll be using in our example:

Java
1class Person { 2 private String firstName; 3 private String lastName; 4 private int age; 5 6 public Person(String firstName, String lastName, int age) { 7 this.firstName = firstName; 8 this.lastName = lastName; 9 this.age = age; 10 } 11 12 @Override 13 public String toString() { 14 return "Person: " + firstName + " " + lastName + ", age " + age; 15 } 16}

Now, let’s see how currying can simplify the process of creating a Person object step by step:

Java
1Function<String, Function<String, Function<Integer, Person>>> curriedPersonCreation = firstName -> lastName -> age -> new Person(firstName, lastName, age); 2 3Function<String, Function<Integer, Person>> withFirstName = curriedPersonCreation.apply("John"); 4 5Function<Integer, Person> withLastName = withFirstName.apply("Doe"); 6 7Person person = withLastName.apply(30); 8 9System.out.println("Created Person: " + person); // Outputs: Created Person: John Doe, age 30

Here’s a step-by-step breakdown of what’s happening:

  1. Defining the Curried Function for Object Creation:

    Java
    1Function<String, Function<String, Function<Integer, Person>>> curriedPersonCreation = firstName -> lastName -> age -> new Person(firstName, lastName, age);

    This curried function curriedPersonCreation is designed to create a Person object. It takes three arguments—firstName, lastName, and age—one at a time. Initially, it takes a firstName and returns a function that expects the lastName, which in turn returns another function that takes the age and finally creates a Person object.

  2. Applying the First Argument (Partial Application):

    Java
    1Function<String, Function<Integer, Person>> withFirstName = curriedPersonCreation.apply("John");

    Here, we apply the firstName ("John") to the curried function. This returns a new function withFirstName that now only needs the lastName and age to complete the object creation.

  3. Applying the Second Argument:

    Java
    1Function<Integer, Person> withLastName = withFirstName.apply("Doe");

    Next, we apply the lastName ("Doe") to the withFirstName function, which returns yet another function withLastName that only needs the age to complete the object.

  4. Finalizing the Object Creation:

    Java
    1Person person = withLastName.apply(30);

    Finally, we apply the age (30) to withLastName, which creates a Person object with the full name "John Doe" and age 30. The result is printed:

    Java
    1System.out.println("Created Person: " + person); // Outputs: Created Person: John Doe, age 30
Why Currying Matters

Currying offers several advantages that can significantly improve your coding practices in Java:

  • Modularity: Currying allows you to break down complex functions into simpler, single-argument functions. This modular approach makes your code easier to understand and maintain.
  • Reusability: By partially applying arguments, curried functions enable you to create reusable, specialized versions of your functions. This leads to more flexible and concise code.
  • Readability: Currying clarifies the intent of your functions, especially when dealing with complex operations or object creation, making your code more readable.

Ready to practice? Let’s move on to the exercises where you’ll get hands-on experience with currying in Java!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.