Understanding Functional Programming Principles
1. Understanding Pure Functions
| Property | Meaning |
|---|---|
| Deterministic | Same input → same output |
| No side effects | No mutation, I/O, exceptions for control flow |
| Referentially transparent | Replace call with value, semantics unchanged |
Example: Pure vs impure
// Pure
int add(int a, int b) { return a + b; }
// Impure (reads clock)
long stamp() { return System.currentTimeMillis(); }
2. Understanding Immutability
| Technique | Java Idiom |
|---|---|
| Final fields | private final |
| Records | record Point(int x, int y) {} Java 16+ |
| Defensive copy | List.copyOf(input) |
| Builder + freeze | Builder produces immutable |
| Persistent collections | Vavr / pcollections |
3. Understanding Higher-Order Functions
| Operation | Java Type |
|---|---|
| Takes function | Function<T,R>, Predicate<T> |
| Returns function | Supplier<Function<T,R>> |
| Consumer | Consumer<T> |
| Bi-arity | BiFunction<T,U,R> |
Example: Function as parameter
<T,R> List<R> map(List<T> xs, Function<T,R> f) {
return xs.stream().map(f).toList();
}
4. Understanding Function Composition
| API | Direction |
|---|---|
f.andThen(g) | g(f(x)) |
f.compose(g) | f(g(x)) |
Predicate and/or/negate | Boolean combinators |
Example: Compose pipeline
Function<String,String> pipeline = ((Function<String,String>) String::trim)
.andThen(String::toLowerCase)
.andThen(s -> s.replace(" ", "-"));
5. Understanding Referential Transparency
| Implication | Benefit |
|---|---|
| Memoization safe | Cache results freely |
| Parallelizable | No shared mutable state |
| Reasoning | Local equational reasoning |
6. Understanding First-Class Functions
| Capability | Java |
|---|---|
| Assign to variable | Function<X,Y> f = x -> ... |
| Pass as arg | Lambda / method reference |
| Return from method | Function<X,Y> build() |
| Store in collection | Map<String, Function<X,Y>> |
7. Implementing Map, Filter, Reduce Patterns
| Op | Stream API | Result |
|---|---|---|
| map | .map(fn) | Transform each |
| filter | .filter(pred) | Subset |
| reduce | .reduce(id, op) | Aggregate |
| flatMap | .flatMap(fn) | Flatten nested |
| collect | .collect(Collectors.toMap(...)) | Materialize |
Example: MFR pipeline
long totalCents = orders.stream()
.filter(Order::isPaid)
.mapToLong(Order::totalCents)
.sum();
8. Understanding Currying and Partial Application
| Concept | Definition |
|---|---|
| Currying | f(a,b,c) → f(a)(b)(c) |
| Partial application | Fix some args, return new function |
Example: Curry in Java
Function<Integer, Function<Integer, Integer>> add = a -> b -> a + b;
Function<Integer, Integer> inc = add.apply(1);
int two = inc.apply(1); // 2
9. Understanding Closures
| Aspect | Java Behavior |
|---|---|
| Captured variable | Must be effectively final |
| Capture scope | Enclosing local + instance fields |
| Mutability workaround | Wrap in AtomicReference / array |
10. Implementing Monads
| Monad | Java Equivalent | Use |
|---|---|---|
| Maybe | Optional<T> | Absence |
| Either | Vavr Either<L,R> | Error or value |
| List | Stream<T> | Nondeterminism |
| Future | CompletableFuture<T> | Async |
Example: flatMap chain
Optional<String> city = Optional.ofNullable(userId)
.flatMap(repo::findUser)
.map(User::address)
.map(Address::city);
11. Understanding Lazy Evaluation
| Mechanism | Java |
|---|---|
| Streams | Intermediate ops are lazy until terminal op |
| Suppliers | Supplier<T> defers computation |
| Memoized lazy | Double-checked volatile field |
12. Avoiding Side Effects
| Pattern | Replacement |
|---|---|
| Mutating param | Return new value |
| Logging in pure fn | Return Writer / events |
| I/O inline | Functional core, imperative shell |
| Exception for control | Return Result type |