Refactoring code to add lambda laziness 2 – Functional style programming – extending API
Fixing in functional fashion
How about providing this fix in a functional programming fashion? Practically, all we want is to lazy download the applicatons’s dependenies. Since laziness is the functional programming spciality, and we’ve just got familiar with the Supplier (see the previous problem) we can start as follows:
public class ApplicationDependency {
private final Supplier<String> dependencies
= this::downloadDependencies;
…
public String getDependencies() {
return dependencies.get();
}
…
private String downloadDependencies() {
return “list of dependencies downloaded from repository ”
+ Math.random();
}
}
First, we defined a Supplier that calls the downloadDependencies() method. We know that the Supplier is lazy so nothing happens until its get() method is explicitly called.Second, we have modified getDependencies() to return dependencies.get(). So, we delay the application’s dependencies downloading until they are explicitly required.Third, we modified the return type of the downloadDependencies() method from void to String. This is needed for the Supplier.get().This is a nice fix but it has a serious shortcoming. We lost the caching! Now, the dependencies will be downloaded at every getDependencies() call.We can avoid this issue via memoization (https://en.wikipedia.org/wiki/Memoization). We have covered this concept in Chapter 8 of The Complete Coding Interview Guide in Java. However, in a nutshell, memoization is a technique used to avoid duplicate work by caching results that can be reused later.Memoization is a technique commonly applied in Dynamic Programming, but there are no restrictions or limitations. For instance, we can apply it in functional programming. In our particular case, we start by defining a functional interface that extends the Supplier interface:
@FunctionalInterface
public interface FSupplier<R> extends Supplier<R> {}
Next, we provide an implementation of FSupplier that basically cashes the unseen results and serve from the cache the already seen ones:
public class Memoize {
private final static Object UNDEFINED = new Object();
public static <T> FSupplier<T> supplier(
final Supplier<T> supplier) {
AtomicReference cache = new AtomicReference<>(UNDEFINED);
return () -> {
Object value = cache.get();
if (value == UNDEFINED) {
synchronized (cache) {
if (cache.get() == UNDEFINED) {
System.out.println(“Caching: ” + supplier.get());
value = supplier.get();
cache.set(value);
}
}
}
return (T) value;
};
}
}
Finally, we replace our initial Supplier with FSupplier as follows:
private final Supplier<String> dependencies
= Memoize.supplier(this::downloadDependencies);
Done! Our functional approach takes advantage of Supplier’s laziness and can cache the results.