functional-reactive
Made in the European Union

Memoizer

Cache the result of any pure function — Supplier, Function, BiFunction or TriFunction.

Memoizer wraps a pure function so subsequent calls with the same arguments are served from cache. The cache lives in a private ConcurrentHashMap per memoized function, so concurrent callers are safe.

The API

All four overloads live on com.svenruppert.functional.memoizer.Memoizer:

static <T>            Supplier<T>            memoize(Supplier<T> supplier);
static <T, U>         Function<T, U>         memoize(Function<T, U> function);
static <T1, T2, R>    BiFunction<T1, T2, R>  memoize(BiFunction<T1, T2, R> biFunc);
static <T1, T2, T3, R> TriFunction<T1, T2, T3, R> memoize(TriFunction<T1, T2, T3, R> triFunc);

Function: classic memoization

Function<Integer, Long> slowFib = Memoizer.memoize(n ->
    n < 2 ? n : slowFib.apply(n - 1) + slowFib.apply(n - 2));

slowFib.apply(40);   // fast — even though the body is naïve

The trick: the recursive lambda calls the memoized function (slowFib), not itself directly. Without memoization, fib(40) performs over a billion calls.

BiFunction & TriFunction: curry-based memoization

Bi- and TriFunctions internally curry into nested Functions and memoize each level, giving you partial caching as a bonus:

BiFunction<Integer, Integer, Integer> multiply = Memoizer.memoize((x, y) -> x * y);
multiply.apply(3, 4);   // computed
multiply.apply(3, 4);   // cached
multiply.apply(3, 5);   // only the inner level recomputes (x=3 partial is reused)

TriFunction<Integer, Integer, Integer, Integer> abc = Memoizer.memoize((a, b, c) -> a * b + c);

Bridging legacy code

A method reference on an instance is just a BiFunction (or TriFunction) — so legacy code can be opportunistically memoized without touching it:

final MyLegacyClass legacy = new MyLegacyClass();
final BiFunction<Integer, Integer, Value> raw   = legacy::doWork;
final BiFunction<Integer, Integer, Value> cached = Memoizer.memoize(raw);

The original method is untouched; cached adds caching for callers who want it.

Cache lifecycle

Each call to Memoizer.memoize(...) returns a new memoized function with its own private cache. Hold the returned reference for as long as you want the cache to live; let it go out of scope and the cache is GC’d along with it.

There is no built-in size limit, eviction, or weak-reference variant. If you need any of those, wrap your own Cache (Caffeine, Guava) and expose it as a Function.

Only memoize pure functions
Memoization is silent: side effects in the wrapped function happen on the first call only. Logging, DB writes, counter increments — none of them fire on subsequent cache hits. If you wrap a non-pure function by accident, debugging the resulting behaviour is unpleasant.