Saturday 17 January 2015

Putting functions to good use

In my previous post I wrote that Java 8 has a new, functional face – and a pretty one. Programming in a functional style is not a theoretical whim of computer scientists, is has immediate practical consequences. Thinking in terms of function composition produces shorter code that is easier to read and, more importantly, more robust.

In this article I will show a few functional little tricks that yield significant benefits.

Efficient assertions

Assertions are a powerful way to create self-verifying code. A programmer who routinely uses assertions writes better code simply because he has to think twice about the invariants of a program.

However, assertions do incur a performance penalty if the asserted expression is complex. This can be addressed in Java 8 by using lazy evaluation via an anonymous function returning a boolean:

Assert.must(() -> 3 == myComplexFunctionThatHasToReturnThree(...));

instead of using the classic assert keyword:

assert 3 == myComplexFunctionThatHasToReturnThree(...);

To do this trick we need a class Assert that contains a method must() which does all the work. Firstly, Assert should be able to detect that assertions are turned on:

public static final boolean ON = true;
private static boolean isEnabled = false;

static {
   
assert isEnabled = true; // side effects intended
}

public static boolean enabled() { return isEnabled; }

The ON flag allows the programmer to remove the asserting code anywhere in his program by leveraging the optimizing capabilities of the Java compiler (the so-called conditional compilation idiom in Java). The isEnabled internal flag is set to true whenever assertions are globally turned on. The enabled() method allows read-only access to isEnabled from anywhere in the program.

The code for the must() method is deceptively trivial:


public static void must(Supplier<Boolean> assertion) {
   
if (ON) {
       
if (enabled()) {
           
assert assertion.get();
       
}
   
}
}

The ON flag eliminates the method altogether when false and the call to enabled() makes sure the evaluation of assertion takes placed only when assertions are turned on. The Appendix contains the full code for the Assert class.

Cheap caching

Caching is a huge industry. There are many solutions out there, both open-source and commercial, because caching is important for database access, web or mobile. Sometimes we don’t need a fancy solution to cache: some extra memory, an internal variable and a small test are enough.

This can be safely and elegantly encapsulated in a Cached<T> class that uses the lambda expressions and Optional<T>. Firstly, Cached<T> needs a value field to store the cached value and a supplier lambda to provide the value to cache. Because initially the value does not exist, value has to be of type Optional<T> and initially empty:

private Optional<T> value;
private final Supplier<T> supplier;

Secondly, we need a get() method to get the cached value. It the value does not exist initially the class has to get it first. This can be achieved in one line of code:


public T get() {
   
return value.orElseGet(() -> (value = Optional.of(supplier.get())).get());
}

Thirdly, sometimes we want to to mark the value “dirty”, meaning that it has to be loaded again when next retrieved. A simple clear() method which resets value to an empty Optional<T> is all we need:


public void clear() {
   
assert supplier != null;
   
value = Optional.empty(); 
}

The Appendix below contains the full code of the Cached<T> class.

Eliminating if (x != null) { … }

This kind of test is really annoying and many times a great source of bugs. Fortunately, in Java 8 we have the Optional<T> type which elegantly expresses that a value may or may not be there. Also, Optional<T> is a map-able object so instead of:


if (x != null) { ... }

we nicely have:


x.map(y -> { ... })

The Optional.orElse() method allows us to have an “else” branch as well:


x.map(y -> { ... })
 
.orElse(<some other value>)

What if the if block contains more than one statement? Then we simply split that block into parts which communicate via intermediate values. In other words, instead of:


if (x != null) {
   
... stmt1 ...
   
... stmt2 ...
   
... stmt3 ...
} else {
   
...
}

we have:


x.map(u -> ... expr1 ...)
 
.map(v -> ... expr2 ...)
 
.map(w -> ... expr3 ...)
 
.orElse( { ... } )

Obviously, expr1 must provide the value for v and expr2 must provide the value for w but this is the whole beauty: in imperative code stmt3 has uncontrolled access to the variables of stmt1 and stmt2 whereas with the functional approach the communication between expr1, expr2 and expr3 is tightly controlled and, therefore, less prone to bugs.

Safe type casting

Cast exceptions are the sibling of null-pointer exceptions: equally annoying and equally unjustified. Performing (T)x is very disruptive when the cast fails and it is as bad as accessing a pointer before ensuring it is different from null.

We do have in Java the instanceof operator as a safeguard but this implies yet another if-else in our code. More code nesting, more bloat. Via Optional<T> class we can have a better alternative, a Cast<T> class:


public class Cast {
   
public static <T, U> Optional<T> as(Class<T> clazz, U obj) {
       
return Optional.ofNullable(clazz.isInstance(obj) ? clazz.cast(obj) : null);
   
}
}

With Cast.as(), casting to something doesn’t throw but it simply returns an Optional of that type. If the cast fails, the Optional is empty. If the cast passes, the Optional contains the value. Simple and elegant.

Cast.as() is not strict, it obeys the substitution principle. If we want strict type casting (useful when down-casting) then we can have a strict() method:

 
public class Cast {
   
public static <T, U> Optional<T> as(Class<T> clazz, U obj) {
       
return Optional.ofNullable(clazz.isInstance(obj) ? clazz.cast(obj) : null);
   
}
   
public static <T, U> Optional<T> strict(Class<T> clazz, U obj) {
       
return Optional.ofNullable(obj != null &&
                                  
clazz.equals(obj.getClass()) ?
                                  
clazz.cast(obj) : null);
   
}
}

After type casting this way, we always obtain an Optional which is better to use than to complicate our code with if-else statements.

Eliminating for(int i; i<n; ++i) loops

This is my favourite. Eliminating cycles with their hard-to-grasp and prone-to-error transitions and invariants is, in my opinion, one of the first-class gains in Java 8. We can eliminate for loops by using Java 8 streams:


for(int i=0; i<n; ++i) {
   
... code using i ...
}

becomes:


IntStream.range(0, n)
        
.map(i -> { ... code using i ... });

There are three big advantages with this code:

  1. The transition from one iteration to another is not our problem. We don’t have to care about ++i.
  2. The code inside the for loop turns into a composition of functions. For details, see above how stmt1, stmt2, stmt3 became expr1, expr2 and expr3.
  3. The whole for loop becomes an expression (a stream value) which can be passed around like any other value and it can be further composed to produce more complex computations.

The functional approach works for nested loops, too:


for(int i=0; i<m; ++i) { 
    for(int j=0; j<n; ++j) {
       
... code using j and i ...
   
}
}

becomes:


IntStream.range(0, m) 
         .mapToObj(i -> IntStream.range(0, n) 
                                
.map(j -> Pair.of(i, j)))
        
.flatMap(s -> s)
        
.map(p -> { 
                      
final int i = p.first; 
                      
final int j = p.second; 
                      
... code using j and i ... 
                      
return p; 
                  
});

Like before, a nested for loop becomes an expression of type stream whose result can be passed around like any value and it can be composed to produce more complex code - but in a safe, controlled manner.

OBS: the code above requires a small Pair<U, V> class to hold both indexes together. Here’s the code for it:


public class Pair<U, V> {
   
public final U first;
   
public final V second;
   
protected Pair(U first, V second) {
       
this.first = first;
       
this.second = second;
   
}
   
public static Pair<U, V> of(U first, V second) {
       
public new Pair(first, second);
   
}
}

Conclusion

By providing lambda expressions and optionality, Java 8 comes with a new pretty face, a functional one. By using functional idioms, the programmer can convert imperative, spaghetti-like code into a pipe of small processing units (anonymous functions) whose compositions (map-able objects) can be passed around allowing for safe construction of more complex computations from smaller parts.

Appendix


public class Assert {

   
public static final boolean ON = true;
   
private static boolean isEnabled = false;

   
static {
       
assert isEnabled = true; // side effects intended
    }

   
public static boolean enabled() { return isEnabled; }

   
public static void must(Supplier<Boolean> assertion) {
       
if (ON) {
           
if (enabled()) {
               
assert assertion.get();
           
}
       
}
   
}
   
public static void must(Supplier<Boolean> assertion, String msg) {
       
if (ON) {
           
if (enabled()) {
               
assert assertion.get(): msg;
           
}
       
}
   
}
   
public static void must(Supplier<Boolean> assertion, Supplier<String> msg) {
       
if (ON) {
           
if (enabled()) {
               
if (!assertion.get()) { assert false: msg.get(); }
           
}
       
}
   
}
}

public final class Cached<T> {

   
private Optional<T> value;
   
private final Supplier<T> supplier;

   
private Cached(Supplier<T> supplier) {
       
if (supplier == null) {
           
throw new IllegalArgumentException("supplier");
       
}

       
this.value = Optional.empty();
       
this.supplier = supplier;
   
}

   
public T get() {
       
return value.orElseGet(() -> (value = Optional.of(supplier.get())).get());
   
}

   
public void clear() {
       
assert supplier != null;
       
value = Optional.empty(); 
   
}

   
public static <T> Cached<T> of(Supplier<T> supplier) {
       
return new Cached(supplier);
   
}
}

No comments:

Post a Comment