Understanding Functional Programming Patterns

1. Understanding Pure Functions

PropertyRequirement
DeterminismSame input → same output
No side effectsNo I/O, mutation, exceptions
Referential transparencyCall replaceable with result
Thread safetyFree by construction
TestabilityNo mocks needed

2. Using Function Composition

CombinatorResult
f.andThen(g)g ∘ f
f.compose(g)f ∘ g
Function.identity()Identity element

Example: Pipeline

Function<String, String> clean = ((Function<String,String>) String::trim)
    .andThen(String::toLowerCase)
    .andThen(s -> s.replaceAll("\\s+", "-"));
clean.apply("  Hello World "); // "hello-world"

3. Understanding Higher-Order Functions

DefinitionExample
Takes functionlist.forEach(Consumer)
Returns functionFunction<A,B> build(Config c)
Bothmap.compute(k, BiFunction)

4. Using Currying and Partial Application

ConceptForm
Curried 2-argFunction<A, Function<B, R>>
Partial appBind one arg, return reduced-arity
vs BiFunctionCurried allows piecewise binding

Example: Currying

Function<Integer, Function<Integer, Integer>> add = a -> b -> a + b;
Function<Integer, Integer> add5 = add.apply(5);
int n = add5.apply(3); // 8

5. Implementing Tail Recursion

AspectJVM Status
Native TCONot supported by HotSpot
WorkaroundTrampoline pattern with thunks
AlternativeConvert to iteration

Example: Trampoline

interface Trampoline<T> {
    T get(); default boolean done() { return true; }
    default Trampoline<T> next() { throw new UnsupportedOperationException(); }
    default T run() { Trampoline<T> t = this; while (!t.done()) t = t.next(); return t.get(); }
}

6. Using Memoization

TechniqueNotes
ConcurrentHashMap.computeIfAbsentThread-safe cache
CaffeineBounded LRU + TTL
Suppliers.memoize (Guava)Single-value lazy init

Example: Generic memoize

static <K, V> Function<K, V> memoize(Function<K, V> f) {
    var cache = new ConcurrentHashMap<K, V>();
    return k -> cache.computeIfAbsent(k, f);
}

7. Understanding Lazy Evaluation

MechanismExample
StreamIntermediate ops deferred until terminal
Supplier<T>Defer computation until get()
Holder patternLazy class init

8. Working with Immutable Collections

FactoryResult
List.of(...)Unmodifiable, no nulls
Set.of(...)Unmodifiable, no dups
Map.of(k1,v1,...)Up to 10 entries
Map.entry() + Map.ofEntries()Many entries
List.copyOf(coll)Defensive snapshot
Collectors.toUnmodifiableList()From stream

9. Understanding Monads

Monad in JDKunitflatMap
Optional<T>Optional.offlatMap
Stream<T>Stream.offlatMap
CompletableFuture<T>completedFuturethenCompose
Note: Monad laws: left identity, right identity, associativity. JDK's monad-like types satisfy these informally.

10. Implementing Map-Reduce Patterns

PhaseStream Op
Mapmap / flatMap
Filterfilter
Reducereduce(identity, op)
Collectcollect(Collectors.X)

Example: Word count

Map<String, Long> counts = Files.lines(path)
    .flatMap(l -> Arrays.stream(l.toLowerCase().split("\\W+")))
    .filter(w -> !w.isBlank())
    .collect(Collectors.groupingBy(w -> w, Collectors.counting()));

11. Using Suppliers for Deferred Execution

Use CaseAPI
Lazy log messagelogger.atInfo().log(() -> "expensive: " + obj)
Default valueopt.orElseGet(() -> loadDefault())
Lazy fieldHolder + Supplier
Custom exceptionorElseThrow(MyEx::new)