Index

Higher-Order Functions and Currying

Java Functional Programming

9.1 Defining and Using Higher-Order Functions

In functional programming, higher-order functions (HOFs) are functions that can either take other functions as parameters or return functions as resultsβ€”or both. This capability enables powerful abstraction and code reuse by letting you build flexible, customizable behavior without duplicating code.

Why Higher-Order Functions Matter

Traditional programming often involves writing many similar functions with small variations. Higher-order functions allow you to capture these variations as functions themselves and pass them around, letting you compose complex behavior from simpler building blocks.

They:

Java Support for Higher-Order Functions

Since Java 8, the introduction of functional interfaces (interfaces with a single abstract method) and lambdas makes working with functions as first-class values possible. Common functional interfaces like Function<T,R>, Predicate<T>, and Consumer<T> facilitate passing behavior around.

Example 1: Function as Parameter

Suppose you want a generic method to transform strings using different operations:

import java.util.function.Function;

public class HigherOrderExample {
    
    // Higher-order function: takes a Function<String,String> as argument
    public static String transformString(String input, Function<String, String> transformer) {
        return transformer.apply(input);
    }

    public static void main(String[] args) {
        String result1 = transformString("hello", s -> s.toUpperCase());
        String result2 = transformString("functional", s -> s + " programming");

        System.out.println(result1); // Output: HELLO
        System.out.println(result2); // Output: functional programming
    }
}

Here, transformString is a higher-order function that accepts a function to customize how the string is transformed.

Example 2: Function Returning a Function

Higher-order functions can also return functions. This pattern is useful for creating configurable behavior:

import java.util.function.Function;

public class FunctionReturningFunction {

    // Returns a function that adds a fixed prefix to input strings
    public static Function<String, String> prefixer(String prefix) {
        return s -> prefix + s;
    }

    public static void main(String[] args) {
        Function<String, String> helloPrefixer = prefixer("Hello, ");
        Function<String, String> errorPrefixer = prefixer("Error: ");

        System.out.println(helloPrefixer.apply("Alice"));  // Output: Hello, Alice
        System.out.println(errorPrefixer.apply("File not found")); // Output: Error: File not found
    }
}

The prefixer method returns a new function customized with the provided prefix, demonstrating how functions can be dynamically created and reused.

Summary

Higher-order functions are fundamental to writing flexible, expressive, and reusable code in functional programming. Java’s functional interfaces and lambdas provide solid support for defining and using these functions. Whether by passing functions as arguments or returning them as results, higher-order functions help encapsulate behavior, reduce duplication, and enable powerful abstractions that improve code quality and clarity.

Index

9.2 Partial Application and Currying in Java

Partial application and currying are powerful functional programming techniques that allow you to create new functions by pre-filling some arguments of existing functions. Both improve code reuse and flexibility by enabling you to configure functions incrementally.

What is Partial Application?

Partial application is the process of fixing a few arguments of a multi-parameter function, producing a new function that takes the remaining arguments. For example, given a function with three parameters, partial application with the first argument fixed results in a function with two parameters.

Use case: When you know some inputs ahead of time, you can create specialized versions of generic functions without rewriting them.

What is Currying?

Currying transforms a function that takes multiple arguments into a sequence of functions, each with a single argument. For instance, a function (a, b, c) -> r becomes a -> (b -> (c -> r)).

Difference: Currying always produces unary functions nested inside each other, while partial application can fix any subset of arguments without fully nesting.

Why Use These Techniques?

Javas Limitations and Workarounds

Java does not have native syntax for currying or partial application, but with functional interfaces and lambdas, we can emulate these patterns.

Example: Currying in Java

import java.util.function.Function;

public class CurryingExample {

    // Curried function: takes 'a' and returns function taking 'b' then 'c'
    static Function<Integer, Function<Integer, Function<Integer, Integer>>> curriedAdd =
        a -> b -> c -> a + b + c;

    public static void main(String[] args) {
        // Use the curried function step by step
        Function<Integer, Function<Integer, Integer>> add5 = curriedAdd.apply(5);
        Function<Integer, Integer> add5And10 = add5.apply(10);
        int result = add5And10.apply(3); // 5 + 10 + 3 = 18

        System.out.println(result); // Output: 18

        // Or all at once
        System.out.println(curriedAdd.apply(1).apply(2).apply(3)); // Output: 6
    }
}

Here, curriedAdd is a function that takes an integer and returns a function expecting the next integer, and so on, until all three inputs are provided.

Example: Partial Application in Java

We can partially apply a function by fixing one or more arguments:

import java.util.function.BiFunction;
import java.util.function.Function;

public class PartialApplicationExample {

    // Regular two-argument function
    static BiFunction<Integer, Integer, Integer> multiply = (a, b) -> a * b;

    // Partial application: fix the first argument
    static Function<Integer, Integer> multiplyBy5 = b -> multiply.apply(5, b);

    public static void main(String[] args) {
        System.out.println(multiply.apply(3, 4)); // 12
        System.out.println(multiplyBy5.apply(4)); // 20
    }
}

The multiplyBy5 function fixes a as 5 and returns a new function that only requires the second argument.

Summary

Index

9.3 Example: Building Reusable Validation Functions

Functional programming encourages building small, reusable functions that can be combined to solve complex problems. Validation is a perfect example: by creating reusable, composable validators, you can easily customize and extend validation logic without duplication.

Using Higher-Order Functions and Currying for Validation

Let’s create a generic validation function that accepts a predicate and an error message. By using partial application, we can fix the error message upfront and later supply the input to validate.

import java.util.function.Function;
import java.util.function.Predicate;
import java.util.ArrayList;
import java.util.List;

public class ValidationExample {

    // Validator type: takes input T, returns list of error messages (empty if valid)
    @FunctionalInterface
    interface Validator<T> {
        List<String> validate(T t);

        // Combines two validators
        default Validator<T> and(Validator<T> other) {
            return t -> {
                List<String> errors = new ArrayList<>(this.validate(t));
                errors.addAll(other.validate(t));
                return errors;
            };
        }
    }

    // Higher-order function to create a validator from a predicate and an error message
    static <T> Function<String, Validator<T>> createValidator(Predicate<T> predicate) {
        return errorMessage -> t -> predicate.test(t) ? List.of() : List.of(errorMessage);
    }

    public static void main(String[] args) {
        // Explicit type parameters help with type inference
        Validator<String> notEmpty = ValidationExample.<String>createValidator(
            s -> s != null && !s.isEmpty()).apply("Must not be empty");

        Validator<String> minLength5 = ValidationExample.<String>createValidator(
            s -> s.length() >= 5).apply("Must be at least 5 characters");

        // Compose validators using 'and' method
        Validator<String> usernameValidator = notEmpty.and(minLength5);

        // Test inputs
        String input1 = "";
        String input2 = "Java";
        String input3 = "Lambda";

        System.out.println("Input1 errors: " + usernameValidator.validate(input1));
        System.out.println("Input2 errors: " + usernameValidator.validate(input2));
        System.out.println("Input3 errors: " + usernameValidator.validate(input3));
    }
}

Explanation

Benefits

This approach leads to more modular, readable, and flexible validation code, leveraging functional programming concepts effectively in Java.

Index